DEV Community

Atilla Baspinar
Atilla Baspinar

Posted on

Angular Lifecycle Hooks

Angular calls a set of lifecycle hook methods on components and directives at specific moments — during creation, every change detection cycle, and finally before destruction. Implementing the corresponding interface (e.g. OnInit, OnDestroy) is optional but recommended for type safety.


1. constructor vs. ngOnInit

The constructor is a TypeScript feature, not an Angular lifecycle hook. Angular calls it before inputs are set, so @Input() values are undefined at that point.

@Component({ selector: 'app-card', template: '' })
export class CardComponent implements OnInit {
  @Input() title = '';

  constructor() {
    console.log(this.title); // always '' — input not yet set
  }

  ngOnInit() {
    console.log(this.title); // correct value, inputs are initialized
  }
}
Enter fullscreen mode Exit fullscreen mode

Rule: Use the constructor only for dependency injection. Use ngOnInit for any initialization logic that depends on inputs or injected services.


2. Lifecycle hooks in order

ngOnChanges(changes: SimpleChanges)

Runs whenever one or more @Input() values change. On the first render it runs before ngOnInit. The changes argument is a map of input name → SimpleChange object, each containing previousValue, currentValue, and firstChange.

ngOnChanges(changes: SimpleChanges) {
  if (changes['title']) {
    const { previousValue, currentValue, firstChange } = changes['title'];
    console.log(`title changed from "${previousValue}" to "${currentValue}"`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Only implements when the component has @Input() properties. Not called if the component has no inputs.


ngOnInit()

Runs once, after all inputs have been set for the first time and ngOnChanges has been called for the first time (if applicable). The component's own template is not yet rendered at this point, but it is safe to read inputs and call services.

ngOnInit() {
  this.data = this.dataService.load(this.id);
}
Enter fullscreen mode Exit fullscreen mode

ngDoCheck()

Runs on every change detection cycle that involves this component — which is very frequent. Use it only when you need to detect changes Angular's default change detection misses (e.g. mutated object references). Avoid heavy logic here.

ngDoCheck() {
  // runs on every CD cycle — keep this extremely lightweight
  if (this.items !== this.previousItems) {
    this.previousItems = this.items;
    this.recompute();
  }
}
Enter fullscreen mode Exit fullscreen mode

ngAfterContentInit()

Runs once after Angular projects external content into the component's <ng-content> slots. This is where you can safely read @ContentChild / @ContentChildren queries.

@ContentChild(HeaderComponent) header!: HeaderComponent;

ngAfterContentInit() {
  console.log(this.header); // projected content is available here
}
Enter fullscreen mode Exit fullscreen mode

ngAfterContentChecked()

Runs after every change detection check of the projected content, including the first. Rarely needed — prefer ngAfterContentInit for one-time setup.


ngAfterViewInit()

Runs once after the component's own template (and all child components within it) has been fully initialized. This is where you can safely read @ViewChild / @ViewChildren queries.

@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;

ngAfterViewInit() {
  const ctx = this.canvasRef.nativeElement.getContext('2d');
  // safe to access DOM here
}
Enter fullscreen mode Exit fullscreen mode

ngAfterViewChecked()

Runs after every change detection check of the component's view. Rarely needed. Be careful — modifying component state here can cause ExpressionChangedAfterItHasBeenChecked errors.


ngOnDestroy()

Runs once, just before Angular removes the component from the DOM. Use it to clean up anything that would otherwise cause memory leaks: timers, subscriptions, event listeners.

export class PollingComponent implements OnInit, OnDestroy {
  private intervalId!: ReturnType<typeof setInterval>;

  ngOnInit() {
    this.intervalId = setInterval(() => {
      this.poll();
    }, 3000);
  }

  ngOnDestroy() {
    clearInterval(this.intervalId); // without this the timer keeps running after the component is gone
  }

  private poll() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

3. DestroyRef — modern alternative to ngOnDestroy (Angular 16+)

DestroyRef is an injectable token that lets you register a cleanup callback anywhere in the component — right next to the setup code, rather than in a separate ngOnDestroy method. This makes the code easier to follow.

export class PollingComponent implements OnInit {
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    const intervalId = setInterval(() => {
      this.poll();
    }, 3000);

    this.destroyRef.onDestroy(() => {
      clearInterval(intervalId); // cleanup lives next to setup
    });
  }

  private poll() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

DestroyRef also works well with RxJS via the takeUntilDestroyed operator from @angular/core/rxjs-interop:

import { Component, inject, OnInit } from '@angular/core';
import { DestroyRef } from '@angular/core';
import { interval } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({ ... })
export class PollingComponent implements OnInit {
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    interval(3000)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.poll());
  }

  private poll() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

The subscription is automatically cancelled when the component is destroyed — no ngOnDestroy needed.


4. Execution order

First render

Order Hook Notes
1 constructor DI only — inputs not yet set
2 ngOnChanges Only if the component has @Input() properties
3 ngOnInit Inputs are ready
4 ngDoCheck
5 ngAfterContentInit Projected content is ready
6 ngAfterContentChecked
7 ngAfterViewInit Component's view and child views are ready
8 ngAfterViewChecked

Every subsequent change detection cycle

Order Hook Notes
1 ngOnChanges Only if an @Input() value changed
2 ngDoCheck
3 ngAfterContentChecked
4 ngAfterViewChecked

On destruction

Order Hook
1 ngOnDestroy

5. Which hooks are used most often?

Hook How common Typical use
ngOnInit Very common Load data, read inputs, subscribe to services
ngOnChanges Common React to input changes
ngOnDestroy / DestroyRef Common Clean up timers, subscriptions
ngAfterViewInit Occasional Access DOM refs, initialize third-party libraries
ngAfterContentInit Occasional Access projected content queries
ngDoCheck Rare Fine-grained change detection
ngAfterViewChecked Rare Post-render DOM reads
ngAfterContentChecked Rare Post-check projected content

Top comments (0)