๐ŸŽฏ Angular Traditional Lifecycle Hooks

All 8 Lifecycle Hooks with Examples

Checked against Angular 22 Hooks Still Supported

1. Complete Lifecycle Hooks Flow

graph TD A[Component Class Instantiated] --> B[Constructor] B --> C[Input Properties Assigned] C --> D[ngOnChanges - First Call] D --> E[ngOnInit] E --> F[ngDoCheck] F --> G[ngAfterContentInit] G --> H[ngAfterContentChecked] H --> I[ngAfterViewInit] I --> J[ngAfterViewChecked] J --> K[Component Ready] K --> L{Change Detection?} L -->|Input Changed| M[ngOnChanges] L -->|Any Change| N[ngDoCheck] M --> O[ngAfterContentChecked] N --> O O --> P[ngAfterViewChecked] P --> L K --> Q{Destroy Component?} Q -->|Yes| R[ngOnDestroy] R --> S[Component Destroyed] Q -->|No| L
๐Ÿ’ก Key Point: Lifecycle hooks execute in a specific order. Understanding this order is crucial for proper component initialization and cleanup.

2. Lifecycle Hooks Quick Reference

Hook Purpose Timing Usage
constructor() Dependency injection, simple initializations First - when class instantiated DI only, avoid complex logic
ngOnChanges() Respond to input property changes Before ngOnInit & when inputs change Input validation, react to changes
ngOnInit() Component initialization after inputs set Once, after first ngOnChanges Fetch data, setup subscriptions
ngDoCheck() Custom change detection Every change detection cycle Manual change detection logic
ngAfterContentInit() After content projection Once, after content initialized Access @ContentChild queries
ngAfterContentChecked() After content checked After every change detection Respond to content changes
ngAfterViewInit() After view initialized Once, after view ready Access @ViewChild, DOM manipulation
ngAfterViewChecked() After view checked After every change detection Respond to view changes
ngOnDestroy() Cleanup before destruction Once, before removal Unsubscribe, clear timers

3. ngOnChanges() - Input Property Changes

graph TD A[Parent Component] --> B[Passes Input to Child] B --> C{First Time?} C -->|Yes| D[ngOnChanges Called] C -->|No| E{Input Value Changed?} E -->|Yes| F[ngOnChanges Called] E -->|No| G[No ngOnChanges] D --> H[SimpleChanges Object] F --> H H --> I[Contains previousValue] H --> J[Contains currentValue] H --> K[Contains firstChange flag] I --> L[Process Change] J --> L K --> L L --> M[Component Updates]

Implementation Example:

@Component({ selector: 'app-user-detail', template: ` <div class="user-card"> <h3>{{ user?.name }}</h3> <p>Age: {{ user?.age }}</p> <p>Status: {{ status }}</p> </div> ` }) export class UserDetailComponent implements OnChanges { @Input() user: User; @Input() status: string; changeLog: string[] = []; ngOnChanges(changes: SimpleChanges): void { // Called before ngOnInit() and whenever one or more inputs change for (const propName in changes) { const change = changes[propName]; const current = JSON.stringify(change.currentValue); const previous = JSON.stringify(change.previousValue); if (change.firstChange) { console.log(`Initial value of ${propName} set to ${current}`); } else { console.log(`${propName} changed from ${previous} to ${current}`); } // React to specific changes if (propName === 'user' && !change.firstChange) { this.validateUser(change.currentValue); } } } private validateUser(user: User): void { // Validation logic if (user.age < 0) { console.warn('Invalid age detected!'); } } }
โš ๏ธ Important: ngOnChanges only detects changes to primitive input values and object references. It won't detect mutations within objects or arrays.

4. ngOnInit() - Component Initialization

graph TD A[Component Constructor] --> B[DI Complete] B --> C[Input Properties Set] C --> D[First ngOnChanges] D --> E[ngOnInit Called Once] E --> F[Initialize Component State] E --> G[Fetch Initial Data] E --> H[Setup Subscriptions] E --> I[Initialize Forms] F --> J[Component Ready] G --> J H --> J I --> J J --> K[Continue Lifecycle]

Implementation Example:

@Component({ selector: 'app-user-list', template: ` <div class="user-list"> @if (loading) { <div>Loading...</div> } @for (user of users; track user.email) { <div>{{ user.name }} - {{ user.email }}</div> } </div> ` }) export class UserListComponent implements OnInit, OnDestroy { @Input() department: string; users: User[] = []; loading = false; private subscription: Subscription; constructor( private userService: UserService, private http: HttpClient, private route: ActivatedRoute ) { // Constructor: Only for DI // DON'T: Make HTTP calls here // DON'T: Access @Input properties (not set yet) console.log('Constructor: department is', this.department); // undefined! } ngOnInit(): void { // Called once after inputs are set // DO: Component initialization logic // DO: HTTP requests // DO: Setup subscriptions console.log('ngOnInit: department is', this.department); // Has value! // 1. Get route params const id = this.route.snapshot.paramMap.get('id'); // 2. Initialize component state this.loading = true; // 3. Fetch initial data this.loadUsers(); // 4. Setup subscriptions this.subscription = this.userService.userUpdates$ .subscribe(update => { console.log('User update received:', update); this.handleUserUpdate(update); }); } private loadUsers(): void { this.userService.getUsers(this.department) .subscribe({ next: (users) => { this.users = users; this.loading = false; }, error: (error) => { console.error('Failed to load users:', error); this.loading = false; } }); } ngOnDestroy(): void { // Clean up subscriptions if (this.subscription) { this.subscription.unsubscribe(); } } }
โœ… Best Practice: Use ngOnInit for component initialization logic, not the constructor. The constructor should only be used for dependency injection.

5. ngDoCheck() - Custom Change Detection

graph TD A[Change Detection Cycle] --> B[ngDoCheck Called] B --> C{Custom Check Needed?} C -->|Yes| D[Check for Changes] D --> E{Changes Detected?} E -->|Yes| F[Update Component State] E -->|No| G[Skip Update] F --> H[Trigger Updates] G --> I[Continue Lifecycle] H --> I I --> J[ngAfterContentChecked] J --> K[ngAfterViewChecked] B --> L[โš ๏ธ Called Very Frequently] L --> M[Performance Impact]

Implementation Example:

@Component({ selector: 'app-differ-check', template: ` <div> <h3>Array Changes: {{ arrayChangeCount }}</h3> @for (item of items; track item.name) { <div>{{ item.name }} - {{ item.value }}</div> } </div> ` }) export class DifferCheckComponent implements DoCheck, OnInit { @Input() items: any[] = []; arrayChangeCount = 0; private differ: IterableDiffer<any>; private oldLength = 0; constructor(private iterableDiffers: IterableDiffers) {} ngOnInit(): void { // Create a differ to track array changes this.differ = this.iterableDiffers.find(this.items).create(); } ngDoCheck(): void { // Called on every change detection run // Use for custom change detection logic // Example 1: Detect array mutations if (this.differ) { const changes = this.differ.diff(this.items); if (changes) { this.arrayChangeCount++; changes.forEachAddedItem(record => { console.log('Added:', record.item); }); changes.forEachRemovedItem(record => { console.log('Removed:', record.item); }); } } // Example 2: Manual deep object checking if (this.items.length !== this.oldLength) { console.log(`Length changed from ${this.oldLength} to ${this.items.length}`); this.oldLength = this.items.length; } // WARNING: Be careful with performance! // This runs VERY frequently } }
๐Ÿšจ Performance Warning: ngDoCheck is called very frequently - on every change detection cycle. Avoid heavy computations here or your app will become sluggish!

6. Content Projection Hooks

graph TD A[Parent Component Template] --> B[ng-content Projection] B --> C[Child Component] C --> D[ngAfterContentInit] D --> E[@ContentChild Available] D --> F[@ContentChildren Available] E --> G[Access Projected Content] F --> G G --> H[Component Renders] H --> I[Change Detection] I --> J[ngAfterContentChecked] J --> K{Content Changed?} K -->|Yes| L[Handle Change] K -->|No| M[Skip] L --> I M --> N[Continue]

Implementation Example:

// Parent Component Template @Component({ selector: 'app-parent', template: ` <app-tabs> <app-tab title="First" #firstTab>First Content</app-tab> <app-tab title="Second">Second Content</app-tab> <app-tab title="Third">Third Content</app-tab> </app-tabs> ` }) export class ParentComponent {} // Tab Component @Component({ selector: 'app-tab', template: `<div><ng-content></ng-content></div>` }) export class TabComponent { @Input() title: string; active = false; } // Tabs Container Component @Component({ selector: 'app-tabs', template: ` <div class="tabs"> <div class="tab-headers"> @for (tab of tabs; track tab) { <button (click)="selectTab(tab)" [class.active]="tab.active"> {{ tab.title }} </button> } </div> <div class="tab-content"> <ng-content></ng-content> </div> </div> ` }) export class TabsComponent implements AfterContentInit, AfterContentChecked { @ContentChildren(TabComponent) tabs: QueryList<TabComponent>; ngAfterContentInit(): void { // Content children are set after the content has been projected console.log(`Found ${this.tabs.length} tabs`); // Select first tab by default if (this.tabs.length > 0) { this.selectTab(this.tabs.first); } // Subscribe to changes in content children this.tabs.changes.subscribe(() => { console.log('Tabs changed!'); }); } ngAfterContentChecked(): void { // Called after every change detection cycle // that checks projected content // Ensure one tab is always active const activeTabs = this.tabs.filter(tab => tab.active); if (activeTabs.length === 0 && this.tabs.length > 0) { this.selectTab(this.tabs.first); } } selectTab(tab: TabComponent): void { // Deactivate all tabs this.tabs.forEach(t => t.active = false); // Activate selected tab tab.active = true; } }

7. View Initialization Hooks

graph TD A[Component Template Rendered] --> B[View Created] B --> C[ngAfterViewInit] C --> D[@ViewChild Available] C --> E[@ViewChildren Available] C --> F[DOM Elements Ready] D --> G[Access Child Components] E --> G F --> H[DOM Manipulation Safe] G --> I[Initialize Third-party Libs] H --> I I --> J[View Complete] J --> K[Change Detection] K --> L[ngAfterViewChecked] L --> M{View Changed?} M -->|Yes| N[Handle View Change] M -->|No| O[Continue] N --> K

Implementation Example:

@Component({ selector: 'app-chart', template: ` <div class="chart-container"> <canvas #chartCanvas></canvas> <app-legend #legend [data]="chartData"></app-legend> </div> <button (click)="updateChart()">Update</button> ` }) export class ChartComponent implements AfterViewInit, AfterViewChecked, OnDestroy { @ViewChild('chartCanvas', { static: false }) canvas: ElementRef<HTMLCanvasElement>; @ViewChild(LegendComponent) legend: LegendComponent; chartData = []; private chart: any; private resizeObserver: ResizeObserver; ngAfterViewInit(): void { // Called once after view is initialized // DOM elements and child components are available console.log('Canvas element:', this.canvas.nativeElement); console.log('Legend component:', this.legend); // 1. Initialize chart library (requires DOM element) this.initializeChart(); // 2. Setup ResizeObserver for responsive charts this.resizeObserver = new ResizeObserver(entries => { this.handleResize(entries[0].contentRect); }); this.resizeObserver.observe(this.canvas.nativeElement); // 3. Communicate with child component this.legend.highlightFirst(); } ngAfterViewChecked(): void { // Called after every change detection cycle that checks the view // Be careful - this runs frequently! // Example: Adjust layout after view updates if (this.shouldAdjustLayout()) { this.adjustChartLayout(); } } private initializeChart(): void { const ctx = this.canvas.nativeElement.getContext('2d'); // Initialize third-party chart library // This MUST be done after view init when canvas exists this.chart = new Chart(ctx, { type: 'line', data: this.chartData, options: { responsive: true } }); } updateChart(): void { // This will trigger ngAfterViewChecked this.chartData = [...this.chartData, Math.random() * 100]; if (this.chart) { this.chart.update(); } } ngOnDestroy(): void { // Clean up resources if (this.resizeObserver) { this.resizeObserver.disconnect(); } if (this.chart) { this.chart.destroy(); } } }
โš ๏ธ ExpressionChangedAfterItHasBeenCheckedError: Don't change component properties that affect the template in ngAfterViewInit or ngAfterViewChecked. This causes the infamous "Expression has changed" error!

8. ngOnDestroy() - Component Cleanup

graph TD A[Component Removal Triggered] --> B[ngOnDestroy Called] B --> C[Unsubscribe Observables] B --> D[Clear Timers/Intervals] B --> E[Disconnect Event Listeners] B --> F[Clean External Resources] B --> G[Cancel HTTP Requests] C --> H[Prevent Memory Leaks] D --> H E --> H F --> H G --> H H --> I[Component Destroyed] I --> J[Removed from DOM] J --> K[Garbage Collection]

Complete Cleanup Example:

@Component({ selector: 'app-dashboard', template: `...` }) export class DashboardComponent implements OnInit, OnDestroy { // Subscriptions private dataSubscription: Subscription; private routeSubscription: Subscription; private destroy$ = new Subject<void>(); // Timers private refreshTimer: any; private animationFrame: number; // Event listeners private resizeHandler = this.onResize.bind(this); private documentClickHandler = this.onDocumentClick.bind(this); // External resources private websocket: WebSocket; private worker: Worker; constructor( private dataService: DataService, private route: ActivatedRoute ) {} ngOnInit(): void { // 1. Setup subscriptions this.dataSubscription = this.dataService.getData() .subscribe(data => console.log(data)); // 2. Using takeUntil pattern (alternative approach) this.dataService.updates$ .pipe(takeUntil(this.destroy$)) .subscribe(update => console.log(update)); // 3. Route params subscription this.routeSubscription = this.route.params .subscribe(params => console.log(params)); // 4. Setup timer this.refreshTimer = setInterval(() => { this.refreshData(); }, 5000); // 5. Setup event listeners window.addEventListener('resize', this.resizeHandler); document.addEventListener('click', this.documentClickHandler); // 6. Setup WebSocket this.websocket = new WebSocket('ws://localhost:8080'); // 7. Setup Web Worker if (typeof Worker !== 'undefined') { this.worker = new Worker(new URL('./data.worker', import.meta.url)); this.worker.onmessage = ({ data }) => { console.log(data); }; } // 8. Animation frame this.animate(); } ngOnDestroy(): void { // Called once, right before component is destroyed console.log('Cleaning up component resources...'); // 1. Unsubscribe from observables if (this.dataSubscription) { this.dataSubscription.unsubscribe(); } if (this.routeSubscription) { this.routeSubscription.unsubscribe(); } // 2. Complete subject for takeUntil pattern this.destroy$.next(); this.destroy$.complete(); // 3. Clear timers if (this.refreshTimer) { clearInterval(this.refreshTimer); } // 4. Remove event listeners window.removeEventListener('resize', this.resizeHandler); document.removeEventListener('click', this.documentClickHandler); // 5. Close WebSocket connection if (this.websocket) { this.websocket.close(); } // 6. Terminate Web Worker if (this.worker) { this.worker.terminate(); } // 7. Cancel animation frame if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); } // 8. Any other cleanup this.cleanupResources(); } private animate(): void { // Animation logic this.animationFrame = requestAnimationFrame(() => { this.animate(); }); } private onResize(event: Event): void { console.log('Window resized'); } private onDocumentClick(event: MouseEvent): void { console.log('Document clicked'); } }
โœ… Memory Leak Prevention: Always clean up in ngOnDestroy! Unsubscribe from observables, clear timers, remove event listeners, and close connections to prevent memory leaks.

9. Common Lifecycle Patterns

Pattern 1: Safe Subscription Management

// Using takeUntil pattern export class SafeComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); ngOnInit(): void { // All subscriptions use takeUntil this.service.data$ .pipe( takeUntil(this.destroy$), filter(data => data !== null), map(data => data.value) ) .subscribe(value => console.log(value)); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }

Pattern 2: Loading Data with Error Handling

export class DataComponent implements OnInit { data$: Observable<Data>; loading = true; error: string | null = null; ngOnInit(): void { this.data$ = this.service.loadData().pipe( tap(() => this.loading = false), retry(2), // Must come before catchError, or it never sees the error catchError(err => { this.error = err.message; this.loading = false; return EMPTY; }), shareReplay(1) ); } }

Pattern 3: Parent-Child Communication

// Parent Component export class ParentComponent implements AfterViewInit { @ViewChild(ChildComponent) child: ChildComponent; ngAfterViewInit(): void { // Child component is available this.child.doSomething(); } } // Child Component export class ChildComponent implements OnChanges { @Input() data: any; @Output() dataChange = new EventEmitter(); ngOnChanges(changes: SimpleChanges): void { if (changes['data'] && !changes['data'].firstChange) { this.processDataChange(changes['data'].currentValue); } } }

10. Complete Lifecycle Execution Order

graph TD subgraph "Initialization Phase" A[1. Constructor] --> B[2. ngOnChanges - First] B --> C[3. ngOnInit] C --> D[4. ngDoCheck - First] D --> E[5. ngAfterContentInit] E --> F[6. ngAfterContentChecked] F --> G[7. ngAfterViewInit] G --> H[8. ngAfterViewChecked] end subgraph "Update Phase - Repeats" H --> I[Input Change or Event] I --> J[9. ngOnChanges - If Input] J --> K[10. ngDoCheck] K --> L[11. ngAfterContentChecked] L --> M[12. ngAfterViewChecked] M --> I end subgraph "Destruction Phase" I --> N[Component Removal] N --> O[13. ngOnDestroy] end

Real Component Example with All Hooks:

@Component({ selector: 'app-complete-lifecycle', template: ` <div> <h3>{{ title }}</h3> <ng-content></ng-content> <div #viewChild>View Child Content</div> </div> ` }) export class CompleteLifecycleComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { @Input() title: string; @ContentChild('content') content: ElementRef; @ViewChild('viewChild') viewChild: ElementRef; private counter = 0; constructor() { console.log(`1. constructor - counter: ${++this.counter}`); console.log(' - DI ready, inputs NOT set'); } ngOnChanges(changes: SimpleChanges): void { console.log(`2. ngOnChanges - counter: ${++this.counter}`); console.log(' - Input properties changed:', changes); } ngOnInit(): void { console.log(`3. ngOnInit - counter: ${++this.counter}`); console.log(' - Component initialization, inputs ARE set'); console.log(` - title: ${this.title}`); } ngDoCheck(): void { console.log(`4. ngDoCheck - counter: ${++this.counter}`); console.log(' - Custom change detection'); } ngAfterContentInit(): void { console.log(`5. ngAfterContentInit - counter: ${++this.counter}`); console.log(' - Content projection complete'); console.log(' - ContentChild available:', this.content); } ngAfterContentChecked(): void { console.log(`6. ngAfterContentChecked - counter: ${++this.counter}`); console.log(' - Content checked'); } ngAfterViewInit(): void { console.log(`7. ngAfterViewInit - counter: ${++this.counter}`); console.log(' - View initialization complete'); console.log(' - ViewChild available:', this.viewChild); } ngAfterViewChecked(): void { console.log(`8. ngAfterViewChecked - counter: ${++this.counter}`); console.log(' - View checked'); } ngOnDestroy(): void { console.log(`9. ngOnDestroy - counter: ${++this.counter}`); console.log(' - Cleanup before destruction'); } }

11. Common Lifecycle Mistakes to Avoid

โŒ Mistake 1: Complex Logic in Constructor

// WRONG - Don't do this! constructor(private http: HttpClient) { this.http.get('/api/data').subscribe(); // โŒ HTTP in constructor console.log(this.inputValue); // โŒ undefined - inputs not set } // CORRECT - Use ngOnInit ngOnInit(): void { this.http.get('/api/data').subscribe(); // โœ… console.log(this.inputValue); // โœ… Has value }

โŒ Mistake 2: Memory Leaks from Subscriptions

// WRONG - Memory leak! ngOnInit(): void { this.service.data$.subscribe(data => { this.data = data; // โŒ Never unsubscribed }); } // CORRECT - Clean up subscription private subscription: Subscription; ngOnInit(): void { this.subscription = this.service.data$.subscribe(data => { this.data = data; }); } ngOnDestroy(): void { this.subscription?.unsubscribe(); // โœ… Cleaned up }

โŒ Mistake 3: Changing Data in Wrong Hooks

// WRONG - Causes ExpressionChangedAfterItHasBeenCheckedError ngAfterViewInit(): void { this.title = 'New Title'; // โŒ Changes template after check } // CORRECT - Use setTimeout or Promise ngAfterViewInit(): void { setTimeout(() => { this.title = 'New Title'; // โœ… Deferred to next cycle }); // OR use Promise Promise.resolve().then(() => { this.title = 'New Title'; // โœ… Microtask queue }); }

โŒ Mistake 4: Heavy Operations in Frequently Called Hooks

// WRONG - Performance killer! ngDoCheck(): void { // โŒ This runs on EVERY change detection! this.expensiveCalculation = this.items.reduce((sum, item) => { return sum + this.complexComputation(item); }, 0); } // CORRECT - Use memoization or compute only when needed private lastItemsLength = 0; private cachedResult = 0; ngDoCheck(): void { // โœ… Only recalculate when items actually change if (this.items.length !== this.lastItemsLength) { this.lastItemsLength = this.items.length; this.cachedResult = this.calculateExpensive(); } }

โŒ Mistake 5: Accessing View/Content Children Too Early

// WRONG - ViewChild not available yet ngOnInit(): void { this.viewChild.doSomething(); // โŒ undefined error! } // CORRECT - Wait for ngAfterViewInit ngAfterViewInit(): void { this.viewChild.doSomething(); // โœ… ViewChild is ready } // For ContentChild, use ngAfterContentInit ngAfterContentInit(): void { this.contentChild.doSomething(); // โœ… ContentChild is ready }

12. Performance Optimization Tips

Tip 1: OnPush Is the Default in Angular 22

@Component({ selector: 'app-optimized', // OnPush is the default, so you don't need to set it. // ChangeDetectionStrategy.Default is deprecated: use Eager to opt out. template: `...` }) export class OptimizedComponent { @Input() data: any; constructor(private cd: ChangeDetectorRef) {} updateData(): void { // Not needed for signals. Without them, tell Angular to check this view. this.data = { ...newData }; this.cd.markForCheck(); } }

Tip 2: Choose a Stable track Expression

// Template ` @for (item of items; track item.id) { <div>{{ item.name }}</div> } ` // track is required. *ngFor and trackBy are deprecated since v20.

Tip 3: Unsubscribe Patterns Comparison

// Pattern 1: Manual unsubscribe private sub: Subscription; ngOnInit() { this.sub = observable.subscribe(); } ngOnDestroy() { this.sub?.unsubscribe(); } // Pattern 2: TakeUntil private destroy$ = new Subject(); ngOnInit() { observable.pipe(takeUntil(this.destroy$)).subscribe(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } // Pattern 3: Async pipe (auto-unsubscribes) data$ = this.service.getData(); // Use in template with | async

13. Testing Lifecycle Hooks

Testing Example:

// Test what each hook does. Spying on hooks on the instance doesn't work, // because Angular reads them from the class. Examples use Vitest. describe('LifecycleComponent', () => { let component: LifecycleComponent; let fixture: ComponentFixture<LifecycleComponent>; const service = { getData: vi.fn() }; beforeEach(() => { service.getData.mockReturnValue(of(mockData)); TestBed.configureTestingModule({ imports: [LifecycleComponent], providers: [{ provide: DataService, useValue: service }] }); fixture = TestBed.createComponent(LifecycleComponent); component = fixture.componentInstance; }); it('should load data in ngOnInit', () => { expect(service.getData).not.toHaveBeenCalled(); // The first detectChanges runs ngOnInit fixture.detectChanges(); expect(component.data).toEqual(mockData); }); it('should react to input changes', () => { // Setting the property directly would not run ngOnChanges fixture.componentRef.setInput('inputValue', 'new value'); fixture.detectChanges(); expect(component.lastChange).toBe('new value'); }); it('should clean up on destroy', () => { fixture.detectChanges(); fixture.destroy(); expect(component.subscription.closed).toBe(true); }); it('should access ViewChild after view init', () => { fixture.detectChanges(); // ngAfterViewInit runs here expect(component.viewChild).toBeDefined(); }); });

14. Moving from Lifecycle Hooks to Modern Patterns

Lifecycle hooks are still supported, so this move is optional.

graph TD A[Traditional Lifecycle] --> B{Migration Path} B --> C[ngOnInit] C --> D[constructor + inject] B --> E[ngOnDestroy] E --> F[DestroyRef.onDestroy] B --> G[ngOnChanges] G --> H[Signal inputs] B --> I[ViewChild/ContentChild] I --> J[viewChild signal query] D --> K[Modern Angular] F --> K H --> K J --> K

Migration Examples:

// OLD: Class-based with lifecycle hooks @Component({...}) export class OldComponent implements OnInit, OnDestroy { @Input() data: string; private subscription: Subscription; constructor(private service: DataService) {} ngOnInit() { this.subscription = this.service.getData().subscribe(); } ngOnDestroy() { this.subscription.unsubscribe(); } } // NEW: Signal-based @Component({...}) export class ModernComponent { // Signal inputs (Angular 17.1+) data = input<string>(); // Services with inject private service = inject(DataService); private destroyRef = inject(DestroyRef); constructor() { // Effect for reactive updates effect(() => { console.log('Data changed:', this.data()); }); // Subscription with auto-cleanup this.service.getData() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); // Cleanup without ngOnDestroy this.destroyRef.onDestroy(() => { console.log('Cleanup logic here'); }); } }

15. Lifecycle Hooks Cheatsheet

๐ŸŽฏ ngOnChanges

  • When: Input properties change
  • Use: React to @Input changes
  • Receives: SimpleChanges object
  • Called: Multiple times

๐Ÿš€ ngOnInit

  • When: After first ngOnChanges
  • Use: Component initialization
  • Receives: Nothing
  • Called: Once only

๐Ÿ” ngDoCheck

  • When: Every change detection
  • Use: Custom change detection
  • Receives: Nothing
  • Called: Very frequently

๐Ÿ“ฆ ngAfterContentInit

  • When: After content projection
  • Use: Access @ContentChild
  • Receives: Nothing
  • Called: Once only

โœ… ngAfterContentChecked

  • When: After content checked
  • Use: Respond to content changes
  • Receives: Nothing
  • Called: After every check

๐ŸŽจ ngAfterViewInit

  • When: After view initialized
  • Use: Access @ViewChild, DOM
  • Receives: Nothing
  • Called: Once only

๐Ÿ‘๏ธ ngAfterViewChecked

  • When: After view checked
  • Use: Respond to view changes
  • Receives: Nothing
  • Called: After every check

๐Ÿงน ngOnDestroy

  • When: Before destruction
  • Use: Cleanup resources
  • Receives: Nothing
  • Called: Once only