EthicsComputerCourses
Join training +91 90532 10052

Lifecycle Hooks

Angular · Lesson 12 of 41 Intermediate 6 min read Updated

A component has a life: Angular creates it, sets its inputs, renders it, updates it many times and finally destroys it when it leaves the page. Lifecycle hooks are methods and functions that Angular calls at these moments, so you can run code at the right time, such as loading data when inputs are ready or stopping a timer when the component disappears.

Signals have made many hooks less necessary, but you will still use a few every day and meet all of them in existing code.

The order of events

Here is a component that records every hook it goes through. Its parent passes a roll number, then changes it, then removes the component:

TypeScript
import {
  AfterViewInit, Component, OnChanges, OnDestroy, OnInit, SimpleChanges,
  afterNextRender, input,
} from '@angular/core';

@Component({
  selector: 'app-hook-logger',
  template: `<p>Roll no: {{ rollNo() }}</p>`,
})
export class HookLogger implements OnChanges, OnInit, AfterViewInit, OnDestroy {
  readonly rollNo = input.required<number>();

  constructor() {
    console.log('constructor');
    afterNextRender(() => console.log('afterNextRender'));
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log(`ngOnChanges: rollNo = ${changes['rollNo']?.currentValue}`);
  }

  ngOnInit() {
    console.log(`ngOnInit: rollNo = ${this.rollNo()}`);
  }

  ngAfterViewInit() {
    console.log('ngAfterViewInit');
  }

  ngOnDestroy() {
    console.log('ngOnDestroy');
  }
}
Output
constructor
ngOnChanges: rollNo = 21
ngOnInit: rollNo = 21
ngAfterViewInit
afterNextRender
--- parent changes roll number to 22
ngOnChanges: rollNo = 22
--- parent removes the component
ngOnDestroy

Implementing the interfaces (OnInit, OnDestroy and so on) is optional, but it makes TypeScript check that you spelled the method names correctly.

The hooks you need most

HookWhen it runsTypical use
constructorThe class is created; inputs are NOT set yetinject() services, create signals and effects
ngOnChangesBefore ngOnInit and whenever an input changesOlder code reacting to input changes
ngOnInitOnce, after the first inputs are setStart work that needs input values
ngAfterViewInitOnce, after the component's view is createdOlder code reading @ViewChild
ngOnDestroyJust before the component is removedStop timers, unsubscribe, save drafts

There are also ngDoCheck, ngAfterContentInit, ngAfterContentChecked and ngAfterViewChecked. They are rarely needed in new code; avoid ngDoCheck and the Checked hooks in particular, because they run on every check and can slow the app down.

Constructor vs ngOnInit

Beginners often ask where to put start-up code. The rule is simple:

  • The constructor is for setting up the object: injecting services, creating signals, computed values and effects. Do not read inputs here; they have no value yet.
  • ngOnInit runs once after Angular has set the inputs, so it is the place for one-time work that needs them, such as logging a page view with a product ID.

With signal inputs, you often need neither: a computed or effect created in the constructor reads the input when it is ready and re-runs when it changes. That is also why ngOnChanges is now rare: computed(() => this.rollNo() * 2) already follows the input.

Cleaning up with DestroyRef

Anything your component starts and does not stop on its own (an interval, a WebSocket, an event listener on window) must be stopped when the component is destroyed, or it keeps running in the background. This is called a memory leak.

ngOnDestroy works, but the modern way is DestroyRef, which lets you register clean-up right next to the code that needs it:

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

@Component({
  selector: 'app-live-clock',
  template: `<p>{{ time() }}</p>`,
})
export class LiveClock {
  protected time = signal(new Date().toLocaleTimeString('en-IN'));

  constructor() {
    const id = setInterval(() => this.time.set(new Date().toLocaleTimeString('en-IN')), 1000);
    inject(DestroyRef).onDestroy(() => clearInterval(id));
  }
}

inject(DestroyRef) must be called in the injection context (the constructor or a field initializer). Effects created in a component already clean up automatically, as you saw in effect & linkedSignal.

Render hooks: afterNextRender and afterEveryRender

Some work must happen only after Angular has updated the real DOM: measuring an element, focusing an input, or starting a third-party library such as a chart or map that needs a real element. Angular provides functions for this:

  • afterNextRender(fn) runs fn once, after the next time Angular renders.
  • afterEveryRender(fn) runs fn after every render. Use it rarely, and keep it fast.
  • afterRenderEffect(fn) is like effect, but runs after rendering and only when the signals it reads have changed.
TypeScript
import { Component, ElementRef, afterNextRender, inject } from '@angular/core';

@Component({
  selector: 'app-seat-map',
  template: `<canvas width="400" height="200"></canvas>`,
})
export class SeatMap {
  private host = inject(ElementRef<HTMLElement>);

  constructor() {
    afterNextRender(() => {
      const canvas = this.host.nativeElement.querySelector('canvas');
      const ctx = canvas?.getContext('2d');
      ctx?.fillRect(10, 10, 40, 40); // draw the first seat
    });
  }
}

These functions never run during server-side rendering, because there is no real browser DOM on the server. That makes them the safe place for code that uses window, document or canvas APIs.

Common mistakes

  • Reading inputs in the constructor. They are not set yet; a required input throws an error. Use ngOnInit, computed or effect.
  • Forgetting to stop timers. An interval in a removed component keeps running and updating signals. Clear it with DestroyRef.onDestroy or ngOnDestroy.
  • Touching the DOM in ngOnInit. The view is not rendered yet, and the code breaks on the server. Use afterNextRender.
  • Heavy work in afterEveryRender or ngDoCheck. These run very often; keep them tiny or avoid them.

Key points

  • Angular calls hooks in order: constructor, ngOnChanges, ngOnInit, ngAfterViewInit, render hooks, and finally ngOnDestroy.
  • Use the constructor for injection and creating signals; use ngOnInit for one-time work that needs inputs.
  • With signal inputs, computed and effect replace most uses of ngOnChanges.
  • Register clean-up with inject(DestroyRef).onDestroy(...) or ngOnDestroy.
  • Use afterNextRender for DOM work; it does not run during server-side rendering.

Exercise

Write a RecordingIndicator component for an online class that shows "Recording: X s" and counts up every second. Start the timer in the constructor and stop it with DestroyRef. In the parent, add a button that toggles the indicator on and off with @if, and check in the browser console (add a console.log in the interval) that the counting stops when the indicator is removed.

Show solution
TypeScript
import { Component, DestroyRef, inject, signal } from '@angular/core';

@Component({
  selector: 'app-recording-indicator',
  template: `<p>Recording: {{ seconds() }} s</p>`,
})
export class RecordingIndicator {
  protected seconds = signal(0);

  constructor() {
    const id = setInterval(() => {
      this.seconds.update((s) => s + 1);
      console.log('tick', this.seconds());
    }, 1000);
    inject(DestroyRef).onDestroy(() => clearInterval(id));
  }
}

@Component({
  selector: 'app-class-room',
  imports: [RecordingIndicator],
  template: `
    <button (click)="recording.set(!recording())">
      {{ recording() ? 'Stop' : 'Start' }} recording
    </button>
    @if (recording()) {
      <app-recording-indicator />
    }
  `,
})
export class ClassRoom {
  protected recording = signal(false);
}

When recording becomes false, @if destroys the indicator, Angular runs the onDestroy callback, and the "tick" messages stop. Starting again creates a fresh component whose count begins at 0.

Quick Quiz

1. Which runs first when a component is created?

2. Where should you start one-time work that needs input values?

3. What is the modern way to register clean-up code in a component?

4. Which function is safe for measuring or drawing on DOM elements, and is skipped on the server?

Learn Angular with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call