Skip to content
Angular ng lifecycle 4 min read

Lifecycle Hooks Overview

Every Angular component and directive moves through a predictable sequence of events from the moment Angular creates it to the moment it is torn down. Lifecycle hooks are the named methods you can implement to run code at each of these moments — initializing data, reacting to input changes, working with the rendered DOM, and cleaning up resources. Knowing the order in which Angular calls them is the difference between code that runs reliably and bugs that surface only under change detection.

How a component’s life unfolds

When Angular instantiates a component it first runs the constructor, then begins a series of phases. Some hooks fire exactly once (creation and destruction), while others fire on every change detection cycle (the “check” hooks). Each hook is declared by an interface from @angular/core whose name is the hook name without the ng prefix — for example, the ngOnInit method comes from the OnInit interface.

Implementing the interface is optional at runtime but strongly recommended: it gives you type checking and signals intent. The hook itself is just a method Angular looks for by name.

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-profile',
  standalone: true,
  template: `<p>{{ name }}</p>`,
})
export class ProfileComponent implements OnInit, OnDestroy {
  name = 'Loading...';

  ngOnInit(): void {
    this.name = 'Ada Lovelace';
  }

  ngOnDestroy(): void {
    console.log('Profile destroyed — clean up here');
  }
}

The hooks and their order

Angular invokes the hooks in a fixed sequence. The table below lists them in the order they run, along with how often each fires.

HookInterfaceWhen it runs
ngOnChangesOnChangesBefore ngOnInit and whenever a data-bound @Input changes
ngOnInitOnInitOnce, after the first ngOnChanges
ngDoCheckDoCheckOn every change detection run
ngAfterContentInitAfterContentInitOnce, after projected content is initialized
ngAfterContentCheckedAfterContentCheckedAfter every check of projected content
ngAfterViewInitAfterViewInitOnce, after the component’s view and child views are initialized
ngAfterViewCheckedAfterViewCheckedAfter every check of the view and child views
ngOnDestroyOnDestroyOnce, just before Angular removes the component

The key distinction is content versus view. Content refers to elements projected in with <ng-content>; view refers to the component’s own template and its child components. Content hooks always fire before the corresponding view hooks.

Tip: ngOnInit runs after the first ngOnChanges, so @Input() values are already set when ngOnInit runs. Put input-dependent initialization in ngOnInit, not the constructor — inputs are undefined inside the constructor.

Watching the sequence in action

A small component that logs each hook makes the order concrete. The name input is set by a parent, so ngOnChanges fires first.

import {
  Component, Input, OnChanges, OnInit, DoCheck,
  AfterContentInit, AfterContentChecked,
  AfterViewInit, AfterViewChecked, OnDestroy,
} from '@angular/core';

@Component({
  selector: 'app-trace',
  standalone: true,
  template: `<span>{{ name }}</span>`,
})
export class TraceComponent
  implements OnChanges, OnInit, DoCheck, AfterContentInit,
    AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
  @Input() name = '';

  ngOnChanges() { console.log('1 ngOnChanges'); }
  ngOnInit() { console.log('2 ngOnInit'); }
  ngDoCheck() { console.log('3 ngDoCheck'); }
  ngAfterContentInit() { console.log('4 ngAfterContentInit'); }
  ngAfterContentChecked() { console.log('5 ngAfterContentChecked'); }
  ngAfterViewInit() { console.log('6 ngAfterViewInit'); }
  ngAfterViewChecked() { console.log('7 ngAfterViewChecked'); }
  ngOnDestroy() { console.log('8 ngOnDestroy'); }
}

Output:

1 ngOnChanges
2 ngOnInit
3 ngDoCheck
4 ngAfterContentInit
5 ngAfterContentChecked
6 ngAfterViewInit
7 ngAfterViewChecked

On every subsequent change detection cycle only the recurring hooks run again — ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked — and ngOnChanges fires only when an input actually changes. ngOnDestroy logs 8 when the component is removed.

Signals and the modern alternatives

Modern Angular increasingly lets you avoid some hooks entirely. Signal inputs (input()) combined with computed() or effect() often replace ngOnChanges, because an effect re-runs automatically when any signal it reads changes.

import { Component, input, effect } from '@angular/core';

@Component({
  selector: 'app-greeter',
  standalone: true,
  template: `<p>Hi, {{ name() }}</p>`,
})
export class GreeterComponent {
  name = input.required<string>();

  constructor() {
    effect(() => console.log('name changed to', this.name()));
  }
}

For cleanup, inject(DestroyRef) with takeUntilDestroyed() is a more composable way to tear down subscriptions than wiring everything through ngOnDestroy. For post-render DOM work, the newer afterNextRender and afterRender callbacks are preferred over ngAfterViewInit in zoneless and SSR-aware code.

Best Practices

  • Initialize input-dependent state in ngOnInit, never the constructor — inputs are not yet set when the constructor runs.
  • Keep ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked extremely cheap; they run on every change detection pass.
  • Always clean up subscriptions, timers, and listeners in ngOnDestroy (or with takeUntilDestroyed) to avoid memory leaks.
  • Avoid mutating view state inside ngAfterViewInit/ngAfterViewChecked to prevent ExpressionChangedAfterItHasBeenCheckedError.
  • Prefer signal inputs, computed(), and effect() over ngOnChanges for reactive derivations in new code.
  • Implement the matching interface for each hook so the compiler verifies your method signatures.
Last updated June 14, 2026
Was this helpful?