#angular #lifecycle #hooks #memory

Day 11 β€” Angular Lifecycle Hooks Deep Dive

πŸ“˜ Day 11 β€” Angular Lifecycle Hooks Deep Dive

Zero to Hero β€” Hands-on Angular Tutorial

Today you will learn:

  • βœ”οΈ The complete Angular Component Lifecycle
  • βœ”οΈ constructor vs. ngOnInit
  • βœ”οΈ ngOnDestroy (Cleanup & Memory Leaks)
  • βœ”οΈ ngAfterViewInit (Accessing the DOM)
  • βœ”οΈ ngOnChanges vs. Signal effect()
  • βœ”οΈ Integrating DestroyRef (Modern Cleanup)

Understanding lifecycle hooks prevents bugs, memory leaks, and performance issues.


🟦 1. The Lifecycle Overview

Angular components go through stages:

  1. Creation
  2. Rendering
  3. Destruction

Angular gives us β€œhooks” (methods) to plug into these moments.

HookTimingUse Case
constructorInstantiationDependency Injection only.
ngOnInitAfter input bindingsAPI calls, initialization logic.
ngOnChangesWhen @Input changesReacting to data changes (Legacy).
ngAfterViewInitAfter template rendersDOM manipulation, Canvas, Charts.
ngOnDestroyBefore removalCleanup, unsubscribing.

🟩 2. constructor() vs. ngOnInit()

The Golden Rule:

  • Constructor: Setup Dependency Injection.
  • ngOnInit: Setup Logic/Data.

❌ BAD:

constructor(private api: ApiService) {
  // Don't fetch data here! Inputs aren't ready yet.
  this.api.getData().subscribe(); 
}

βœ… GOOD:

constructor(private api: ApiService) {}

ngOnInit() {
  // Inputs are ready. Safe to fetch data.
  this.api.getData().subscribe();
}

🟧 3. Cleanup with ngOnDestroy (Crucial!)

If you use setInterval, addEventListener, or manual subscribe(), you MUST clean it up. Otherwise, you get memory leaks.

Legacy Way:

export class Component implements OnDestroy {
  intervalId: any;

  ngOnInit() {
    this.intervalId = setInterval(() => console.log('Tick'), 1000);
  }

  ngOnDestroy() {
    clearInterval(this.intervalId);
    console.log('Cleanup done!');
  }
}

Modern Way (DestroyRef): Available in Angular 16+.

import { Component, inject, DestroyRef } from '@angular/core';

@Component({...})
export class ModernComponent {
  destroyRef = inject(DestroyRef);

  ngOnInit() {
    const id = setInterval(() => console.log('Tick'), 1000);

    // Register cleanup callback right where you create the side effect
    this.destroyRef.onDestroy(() => clearInterval(id));
  }
}

πŸŸ₯ 4. Accessing the DOM: ngAfterViewInit

Sometimes you need to access a generic HTML element (e.g., for a Chart.js canvas or focus management).

Template:

<input #myInput type="text" />

Component:

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

export class FocusComponent implements AfterViewInit {
  @ViewChild('myInput') input!: ElementRef;

  ngAfterViewInit() {
    // DOM is now guaranteed to exist
    this.input.nativeElement.focus();
  }
}

⚠️ Warning: Do NOT modify data in ngAfterViewInit. It causes the dreaded ExpressionChangedAfterItHasBeenCheckedError.


🟫 5. Reacting to Changes: ngOnChanges vs. Signals

Legacy Way (ngOnChanges): Complex to maintain.

@Input() userId = '';

ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    this.loadUser(changes['userId'].currentValue);
  }
}

Modern Way (Signal Inputs + Effects): Much cleaner.

userId = input.required<string>();

constructor() {
  effect(() => {
    // Automatically runs whenever userId() changes
    this.loadUser(this.userId());
  });
}

πŸŽ‰ End of Day 11 β€” What You Learned

Today you mastered the lifecycle:

  • βœ”οΈ Initialization: Logic goes in ngOnInit.
  • βœ”οΈ Cleanup: Always stop intervals/streams in ngOnDestroy or DestroyRef.
  • βœ”οΈ DOM Access: Use ngAfterViewInit.
  • βœ”οΈ Reactivity: Prefer Signals/Effects over ngOnChanges.

You are now writing safe, leak-free code. πŸ›‘οΈ


πŸ§ͺ Day 11 Challenge

Build a β€œCountdown Timer” Component.

Requirements:

  1. Accepts an input seconds (number).
  2. Starts counting down immediately in ngOnInit.
  3. Updates the title document (Browser Tab Title) every second.
  4. Crucial: Stops the timer if the user leaves the page component (ngOnDestroy).