A comprehensive static code analysis tool for Angular TypeScript projects that implements TSLint rules enforcing Angular's official style guide.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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 appropriatengDoCheckInstall with Tessl CLI
npx tessl i tessl/npm-codelyzer