Angular lifecycle validation rules that ensure proper implementation and usage of Angular lifecycle methods and interfaces.
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
}
}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
}
}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
}
}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;
};
}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
}
}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();
}
}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);
}
}
}{
"rules": {
"use-lifecycle-interface": true,
"no-lifecycle-call": true,
"no-conflicting-lifecycle": true
}
}ngOnInit for initial data loadingngOnChanges for input-dependent data loadingngOnDestroy for subscription cleanupngAfterViewInit for DOM manipulationngAfterViewChecked for view-dependent calculationsngDoCheck for custom change detection (sparingly)ngOnChanges for input change reactionsOnDestroy to prevent memory leaksOnPush change detection strategy when appropriatengDoCheck