or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility-rules.mdangular-framework.mdcomponent-rules.mddecorator-metadata-rules.mddirective-rules.mdindex.mdinput-output-rules.mdlifecycle-rules.mdpipe-rules.mdstyle-architecture-rules.mdtemplate-rules.md
tile.json

lifecycle-rules.mddocs/

Lifecycle Rules

Angular lifecycle validation rules that ensure proper implementation and usage of Angular lifecycle methods and interfaces.

Capabilities

Use Lifecycle Interface Rule

Enforces implementation of lifecycle interfaces when using lifecycle methods.

/**
 * Enforces implementation of Angular lifecycle interfaces
 * Ensures components/directives implement proper interfaces for lifecycle methods
 */
export class UseLifecycleInterfaceRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Good with lifecycle interfaces:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-user',
  template: '...'
})
export class UserComponent implements OnInit, OnDestroy {
  private subscription: Subscription;

  ngOnInit(): void {
    this.subscription = this.userService.getUsers().subscribe(users => {
      this.users = users;
    });
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

Bad without interfaces:

@Component({
  selector: 'app-user',
  template: '...'
})
export class UserComponent {  // Missing OnInit, OnDestroy interfaces
  ngOnInit(): void {
    // Implementation without interface
  }

  ngOnDestroy(): void {
    // Implementation without interface
  }
}

No Lifecycle Call Rule

Prevents manual calls to Angular lifecycle methods.

/**
 * Prevents manual calls to Angular lifecycle methods
 * Lifecycle methods should only be called by Angular framework
 */
export class NoLifecycleCallRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Bad manual lifecycle calls:

export class UserComponent implements OnInit {
  ngOnInit(): void {
    this.loadData();
  }

  private loadData(): void {
    // Some logic
    this.ngOnInit(); // ❌ Manual call to lifecycle method
  }

  refreshData(): void {
    this.ngOnDestroy(); // ❌ Manual call to lifecycle method
    this.ngOnInit();    // ❌ Manual call to lifecycle method
  }
}

Good approach:

export class UserComponent implements OnInit {
  ngOnInit(): void {
    this.loadData();
  }

  private loadData(): void {
    // Extract common logic to separate method
    this.fetchUserData();
  }

  refreshData(): void {
    // Call the extracted method instead
    this.fetchUserData();
  }

  private fetchUserData(): void {
    // Common data loading logic
  }
}

No Conflicting Lifecycle Rule

Prevents conflicting lifecycle method implementations.

/**
 * Prevents conflicting lifecycle method implementations
 * Ensures lifecycle methods are implemented consistently
 */
export class NoConflictingLifecycleRule extends Lint.Rules.AbstractRule {
  static readonly metadata: Lint.IRuleMetadata;
  static readonly FAILURE_STRING: string;
  apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}

Usage Examples:

Example of potential conflicts:

export class ConflictingComponent implements OnInit, AfterViewInit {
  ngOnInit(): void {
    // Initialization logic
    this.initializeComponent();
  }

  ngAfterViewInit(): void {
    // Should not conflict with ngOnInit
    this.initializeView(); // ✅ Different, complementary logic
  }

  // ❌ Avoid duplicate initialization patterns
  private initializeComponent(): void {
    this.setupData();
  }

  private initializeView(): void {
    this.setupData(); // Potential conflict - same method called from different lifecycle hooks
  }
}

Angular Lifecycle Methods

Component Lifecycle Order

interface AngularLifecycleInterfaces {
  OnChanges: {
    ngOnChanges(changes: SimpleChanges): void;
  };
  OnInit: {
    ngOnInit(): void;
  };
  DoCheck: {
    ngDoCheck(): void;
  };
  AfterContentInit: {
    ngAfterContentInit(): void;
  };
  AfterContentChecked: {
    ngAfterContentChecked(): void;
  };
  AfterViewInit: {
    ngAfterViewInit(): void;
  };
  AfterViewChecked: {
    ngAfterViewChecked(): void;
  };
  OnDestroy: {
    ngOnDestroy(): void;
  };
}

Lifecycle Hook Usage Patterns

import { 
  Component, 
  OnInit, 
  OnDestroy, 
  OnChanges, 
  AfterViewInit,
  SimpleChanges 
} from '@angular/core';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-lifecycle-example',
  template: '...'
})
export class LifecycleExampleComponent 
  implements OnInit, OnDestroy, OnChanges, AfterViewInit {
  
  @Input() data: any;
  private subscriptions: Subscription[] = [];

  ngOnChanges(changes: SimpleChanges): void {
    // React to input property changes
    if (changes['data'] && !changes['data'].firstChange) {
      this.handleDataChange(changes['data'].currentValue);
    }
  }

  ngOnInit(): void {
    // Component initialization
    this.subscriptions.push(
      this.dataService.getData().subscribe(data => {
        this.processData(data);
      })
    );
  }

  ngAfterViewInit(): void {
    // View initialization complete
    this.initializeViewComponents();
  }

  ngOnDestroy(): void {
    // Cleanup
    this.subscriptions.forEach(sub => sub.unsubscribe());
  }

  private handleDataChange(newData: any): void {
    // Handle input changes
  }

  private processData(data: any): void {
    // Process received data
  }

  private initializeViewComponents(): void {
    // Initialize view-dependent components
  }
}

Best Practices

Memory Management

export class MemoryManagedComponent implements OnInit, OnDestroy {
  private destroy$ = new Subject<void>();

  ngOnInit(): void {
    this.dataService.getData()
      .pipe(takeUntil(this.destroy$))
      .subscribe(data => {
        this.handleData(data);
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Conditional Lifecycle Logic

export class ConditionalComponent implements OnInit, OnChanges {
  @Input() feature: string;
  
  ngOnInit(): void {
    // Always runs on initialization
    this.baseInitialization();
  }

  ngOnChanges(changes: SimpleChanges): void {
    // Only runs when inputs change
    if (changes['feature'] && this.feature) {
      this.initializeFeature(this.feature);
    }
  }
}

Rule Configuration

Example Lifecycle Rules Configuration

{
  "rules": {
    "use-lifecycle-interface": true,
    "no-lifecycle-call": true,
    "no-conflicting-lifecycle": true
  }
}

Common Lifecycle Patterns

Data Loading

  • Use ngOnInit for initial data loading
  • Use ngOnChanges for input-dependent data loading
  • Use ngOnDestroy for subscription cleanup

View Manipulation

  • Use ngAfterViewInit for DOM manipulation
  • Use ngAfterViewChecked for view-dependent calculations

Change Detection

  • Use ngDoCheck for custom change detection (sparingly)
  • Use ngOnChanges for input change reactions

Performance Optimization

  • Implement OnDestroy to prevent memory leaks
  • Use OnPush change detection strategy when appropriate
  • Minimize logic in frequently called hooks like ngDoCheck