Tutorials Logic, IN info@tutorialslogic.com

Angular Lifecycle Hooks ngOnInit to Destroy

Component Lifecycle at a Glance

Read the flow from one-time creation through repeated checks to final cleanup. Render callbacks run after Angular commits the application DOM.
  1. Create constructor and input assignment
  2. Initialize ngOnChanges, ngOnInit, ngDoCheck
  3. Content ngAfterContentInit and checked
  4. View ngAfterViewInit and checked
  5. Render afterNextRender or afterEveryRender
  6. Destroy ngOnDestroy or DestroyRef cleanup

Lifecycle Purpose

Lifecycle hooks describe when Angular creates inputs, initializes a view, checks it, renders it, and destroys it. Use a hook only when work truly depends on that lifecycle stage, and always pair subscriptions, observers, and timers with cleanup.

Angular creates a component instance, assigns its inputs, checks projected content and the component view, renders DOM updates, and eventually destroys the instance. Lifecycle APIs let code run at a precise boundary in that sequence.

A constructor is ordinary TypeScript class setup, not an Angular lifecycle hook. Keep dependency injection and field initialization there. Use a hook only when the work depends on inputs, queries, rendering, repeated checks, or destruction.

  • Implementing OnInit, OnChanges, and the other lifecycle interfaces is optional at runtime, but the interfaces let TypeScript catch misspelled hook names.
  • Angular visits each component once during a change-detection traversal. Avoid changing unrelated state inside checked hooks because that can make the rendered value disagree with the value Angular already checked.
  • afterNextRender and afterEveryRender are application-wide render callbacks, not component class methods. Register them in an injection context such as a constructor.

Lifecycle Sequence

Hook When it runs Common use
constructor When JavaScript creates the class instance Inject dependencies and initialize fields
ngOnChanges() After Angular assigns one or more inputs; first run precedes ngOnInit Compare current and previous input values
ngOnInit() Once after initial inputs, before the component view initializes Initialize state that depends on inputs
ngDoCheck() Whenever Angular checks this component Rare manual detection of changes Angular cannot observe
ngAfterContentInit() Once after projected content initializes Read a decorator-based ContentChild query
ngAfterContentChecked() After every content check Rare observation of checked projected content
ngAfterViewInit() Once after the component view and child views initialize Read a decorator-based ViewChild query
ngAfterViewChecked() After every view check Rare observation after child views are checked
afterNextRender() Once, after the next complete application render One-time DOM measurement or third-party widget setup
afterEveryRender() After every complete application render Repeated DOM synchronization when unavoidable
ngOnDestroy() Once immediately before instance destruction Stop timers, observers, listeners, and long-lived streams

Lifecycle API Definitions

Each API below has a different timing guarantee. Implement only the boundaries the component needs; an ordinary component often needs none of them.

constructor

The constructor runs when JavaScript creates the class instance. Angular has established an injection context, but input values and the component view are not initialized yet.

Use it for dependency injection, field setup, and registering APIs that require an injection context. Do not read inputs, view queries, or projected content here.

ngOnChanges

ngOnChanges runs after Angular assigns one or more inputs. Its first call happens before ngOnInit, and later calls contain only the inputs assigned during that update.

Use SimpleChanges to compare previousValue and currentValue or detect firstChange. It observes Angular input assignments, not deep mutation inside an unchanged object reference.

ngOnInit

ngOnInit runs once after all initial inputs have values and before the component view is initialized.

Use it for one-time state setup that depends on inputs. Do not read view children or rendered DOM here; use a view hook or render callback for that timing.

ngDoCheck

ngDoCheck runs whenever Angular checks this component, including the initial check after ngOnInit.

Use it only for a narrowly measured change that normal inputs, signals, or immutable updates cannot represent. It is a hot path, so avoid network calls, subscriptions, deep comparisons, and state churn.

ngAfterContentInit

ngAfterContentInit runs once after content projected through ng-content has initialized.

Use it to read initialized decorator-based ContentChild or ContentChildren queries. Reading is safe; changing already-checked state here can cause ExpressionChangedAfterItHasBeenCheckedError.

ngAfterContentChecked

ngAfterContentChecked runs after every check of the component's projected content, not merely once during creation.

Because it runs frequently, prefer reactive content queries and computed state. Use this hook only when repeated post-check observation is unavoidable, and do not mutate checked state inside it.

ngAfterViewInit

ngAfterViewInit runs once after the component's own template and child views initialize.

Use it to read initialized decorator-based ViewChild or ViewChildren queries. For DOM measurement after the browser-facing render is committed, afterNextRender is the more precise boundary.

ngAfterViewChecked

ngAfterViewChecked runs after every check of the component view and its child views.

It is a frequent hook and usually signals that a reactive design would be clearer. Avoid changing checked state or performing expensive DOM work here.

afterNextRender

afterNextRender registers a callback that runs once after the next complete application render to the DOM. It is a standalone function, not a class method.

Register it in an injection context for one-time browser DOM work or measurement. It does not run during server-side rendering or build-time prerendering.

afterEveryRender

afterEveryRender registers a callback after every complete application render. Like afterNextRender, it is application-wide rather than tied to one component instance.

Use it only for repeated synchronization with a non-reactive DOM library. Keep the callback small and use write and read phases when layout properties are involved.

ngOnDestroy

ngOnDestroy runs once immediately before Angular destroys the component or directive instance.

Use it to stop timers, observers, listeners, sockets, and other work that can outlive the view. DestroyRef and takeUntilDestroyed can keep setup and teardown closer together when that is clearer.

Initialization

ngOnInit runs exactly once after Angular has supplied the initial input values. It is appropriate for deriving initial state or starting work that depends on those values. It runs before the component's own template has been initialized, so view queries and rendered DOM are not ready.

Do not move every constructor statement into ngOnInit. Field defaults and dependency injection belong with class construction; lifecycle-dependent work belongs in the hook.

ngOnInit Example

ngOnInit Example
import { Component, OnInit, input, signal } from '@angular/core';

@Component({
  selector: 'app-stock-level',
  template: `<p>{{ productCode() }}: {{ status() }}</p>`
})
export class StockLevelComponent implements OnInit {
  productCode = input.required<string>();
  status = signal('Not checked');

  ngOnInit(): void {
    this.status.set(`Ready to check ${this.productCode()}`);
  }
}
Output
For productCode="KB-42": KB-42: Ready to check KB-42

The required input has its initial value when ngOnInit runs. The example derives initial display state from that value without assuming the view DOM already exists.

Input Changes

ngOnChanges runs whenever Angular assigns changed inputs. Its SimpleChanges object contains one entry per changed property, including previousValue, currentValue, and firstChange. During creation, the first ngOnChanges runs before ngOnInit.

The SimpleChanges key is the TypeScript property name even when an input has a public alias. Mutating a property inside the same object does not represent a new input assignment; prefer immutable replacement when a child must observe that change.

ngOnChanges Example

ngOnChanges Example
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
    selector: 'app-child',
    standalone: true,
    template: `<p>{{ title }}</p>`
})
export class ChildComponent implements OnChanges {
    @Input() title = '';
    @Input() count = 0;

    ngOnChanges(changes: SimpleChanges) {
        if (changes['title']) {
            const prev = changes['title'].previousValue;
            const curr = changes['title'].currentValue;
            console.log(`title changed: ${prev} -> ${curr}`);
        }
        if (changes['count']?.firstChange) {
            console.log('count set for the first time:', this.count);
        }
    }
}
// Signal inputs can also be read in ngOnChanges.
// Use the TypeScript property name as the SimpleChanges key.

Guard each entry because only the inputs changed in the current update appear in SimpleChanges. firstChange distinguishes the initial assignment from later updates.

Cleanup

ngOnDestroy runs once before Angular removes the component, such as after route navigation or when an @if branch stops rendering it. Clean up work that can continue independently of the view: intervals, DOM listeners, observers, WebSockets, and long-lived subscriptions.

Modern Angular can keep setup and teardown together with DestroyRef. RxJS code can use takeUntilDestroyed so the subscription completes with the component instead of maintaining a Subscription field manually.

ngOnDestroy Example

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

@Component({
    selector: 'app-timer',
    standalone: true,
    template: `<p>Elapsed: {{ seconds() }}s</p>`
})
export class TimerComponent {
  private destroyRef = inject(DestroyRef);
  seconds = signal(0);

  constructor() {
    interval(1000)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.seconds.update(value => value + 1));
  }
}

takeUntilDestroyed completes the interval subscription when Angular destroys this component. A finite HttpClient request normally completes by itself, while an interval does not.

View Initialization

The component view is the markup declared in its own template. ngAfterViewInit runs once after that view and its child views initialize, which makes decorator-based view queries available. Signal query functions such as viewChild stay reactive as the template changes.

Reading a query is safe here; changing checked state in this hook can trigger ExpressionChangedAfterItHasBeenCheckedError. For DOM work that must happen after the application has committed a render, prefer afterNextRender.

ngAfterViewInit Example

ngAfterViewInit Example
import { AfterViewInit, Component, ElementRef, viewChild } from '@angular/core';

@Component({
    selector: 'app-focus',
    template: `<input #nameInput type="text" placeholder="Auto-focused" />`
})
export class FocusComponent implements AfterViewInit {
  nameInput = viewChild.required<ElementRef<HTMLInputElement>>('nameInput');

  ngAfterViewInit(): void {
    this.nameInput().nativeElement.focus();
  }
}

The required query is valid because the input is always present. If an @if could remove it, use optional viewChild and handle undefined.

Content Hooks

Content means markup projected into a component through ng-content. ngAfterContentInit runs once after projected children initialize; ngAfterContentChecked runs after each later content check. These hooks concern content supplied by the parent, not elements declared in the component's own template.

Use ngAfterContentInit for a decorator-based ContentChild read. Avoid ngAfterContentChecked unless no reactive alternative exists because it runs frequently, and do not mutate already-checked state from either hook.

Projected Content Query

Projected Content Query
import { AfterContentInit, Component, contentChild, Directive } from '@angular/core';

@Directive({ selector: '[cardTitle]' })
export class CardTitleDirective {}

@Component({
  selector: 'app-card',
  template: `<section><ng-content /></section>`
})
export class CardComponent implements AfterContentInit {
  title = contentChild(CardTitleDirective);

  ngAfterContentInit(): void {
    console.log('Projected title present:', Boolean(this.title()));
  }
}

contentChild returns a signal that Angular keeps current. The hook marks the point at which projected content has completed its first initialization.

Custom Checks

ngDoCheck runs whenever Angular checks the component, including the first pass after ngOnInit. It exists for manual change tracking that normal input assignments, signals, and immutable state cannot express.

Because the hook is hot-path code, never perform network requests, create subscriptions, or run expensive deep comparisons in it. Prefer signals, computed values, input transforms, or an iterable/key-value differ when those tools model the change.

  • Use ngDoCheck only after identifying a specific state change Angular cannot otherwise observe.
  • Keep the comparison small and update the remembered baseline after detecting a change.
  • Profile the component if the check examines collections or runs in a large repeated list.

Render Callbacks

afterNextRender runs once after the next complete application render. afterEveryRender runs after every complete render. They are standalone functions registered in an injection context and are the correct boundary for DOM work that must observe committed layout.

Render callbacks do not run during server-side rendering or build-time prerendering. Optional write and read phases help avoid layout thrashing by grouping DOM writes before measurements.

Measure After Rendering

Measure After Rendering
import { Component, ElementRef, afterNextRender, inject } from '@angular/core';

@Component({
  selector: 'app-chart-shell',
  template: `<div class="chart-host">Chart</div>`
})
export class ChartShellComponent {
  private host = inject(ElementRef<HTMLElement>);

  constructor() {
    afterNextRender({
      write: () => {
        this.host.nativeElement.style.padding = '16px';
      },
      read: () => {
        console.log(this.host.nativeElement.getBoundingClientRect().height);
      }
    });
  }
}

The write phase changes layout first; the read phase measures after all scheduled writes. The callback is skipped during SSR, so browser-only DOM APIs remain inside the browser render boundary.

Hook Selection

Choose the narrowest lifecycle boundary. Use ngOnChanges for assigned inputs, ngOnInit for one-time input-dependent setup, query signals for reactive child references, afterNextRender for committed DOM layout, and DestroyRef or ngOnDestroy for teardown.

Need Preferred API Avoid
Derive a value from reactive state computed() Recomputing it in ngDoCheck
React to a signal input computed() or effect() for a true side effect Duplicating derived state in ngOnChanges
Read a child that may appear or disappear viewChild() or contentChild() signal Assuming a required query always exists
Measure rendered layout afterNextRender() Reading layout during checked hooks
End a long-lived RxJS stream takeUntilDestroyed() A forgotten manual subscription array
Confirm the page outcome

Lifecycle Review

5 checks
  • I can place constructor, input-dependent setup, view queries, render-time DOM work, and cleanup at different lifecycle boundaries.
  • I can explain why the first ngOnChanges runs before ngOnInit and read SimpleChanges safely.
  • I can distinguish projected content hooks from component view hooks.
  • I know that ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked run frequently and should be exceptional tools.
  • I can use DestroyRef or takeUntilDestroyed for teardown and afterNextRender for browser DOM measurement.

Lifecycle Review Questions

ngOnChanges reacts when Angular assigns a new value to the input binding. Mutating a nested property preserves the same object reference, so Angular may have no input assignment to report through SimpleChanges.

Clean up streams that can outlive the component: intervals, WebSockets, shared subjects, DOM events, and similar sources. A normal HttpClient request completes, and the async pipe cleans up its own subscription. For long-lived RxJS work, takeUntilDestroyed is usually tidier than maintaining a collection of Subscription objects.

The required query assumes that the matching element or child component exists when the view query is evaluated. In the page example, #nameInput is always present, so reading it in ngAfterViewInit is safe. If the input is placed inside an @if block that is false during initialization, the required query has no result and fails.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.