AngularJSComponent Lifecycle Hooks

Component Lifecycle Hooks

Every Angular component goes through a series of lifecycle phases — from creation to destruction. Angular calls specific methods (called lifecycle hooks) at each phase, giving you a chance to run code at the right moment.

Lifecycle hooks are interfaces you implement on the component class. When Angular detects a class implements one, it calls that method at the appropriate time.

The Complete Lifecycle Sequence

The hooks run in this order when a component is created, used, and destroyed:

Text
Constructor()           ← Not a lifecycle hook, but runs first

ngOnChanges()           ← 1st: fires before ngOnInit if there are @Input() bindings
                           Also fires whenever an @Input() value changes

ngOnInit()              ← 2nd: fires once after the first ngOnChanges

ngDoCheck()             ← 3rd: fires on every change detection cycle (use carefully)

ngAfterContentInit()    ← 4th: fires once after ng-content is initialized
ngAfterContentChecked() ← 5th: fires after every ng-content check

ngAfterViewInit()       ← 6th: fires once after the component's view (and child views) are initialized
ngAfterViewChecked()    ← 7th: fires after every view check

ngOnDestroy()           ← Last: fires just before the component is removed from the DOM
Lifecycle Hooks Quick Reference

Hook

Interface

When it fires

Common Use

ngOnChanges

OnChanges

Before ngOnInit and on every @Input change

React to input changes, validate inputs

ngOnInit

OnInit

Once, after first ngOnChanges

Fetch initial data, setup subscriptions

ngDoCheck

DoCheck

Every change detection run

Custom change detection (rarely needed)

ngAfterContentInit

AfterContentInit

Once, after ng-content projected

Access @ContentChild queries

ngAfterContentChecked

AfterContentChecked

After every content check

Respond to projected content changes

ngAfterViewInit

AfterViewInit

Once, after component view initialized

Access @ViewChild DOM elements

ngAfterViewChecked

AfterViewChecked

After every view check

Respond to view changes (rare)

ngOnDestroy

OnDestroy

Just before component is destroyed

Unsubscribe, clean up timers, close connections

ngOnInit — The Most Important Hook

ngOnInit is the hook you will use most often. It runs once, after all @Input values have been set. Use it for:

  • Fetching initial data from a service
  • Setting up subscriptions
  • Initialization logic that depends on @Input values

TS
import { Component, Input, OnInit, inject } from '@angular/core';
import { ProductService } from '../services/product.service';

interface Product { id: number; name: string; price: number; }

@Component({
  selector: 'app-product-detail',
  standalone: true,
  template: `
    @if (product) {
      <h2>{{ product.name }}</h2>
      <p>Price: {{ product.price | currency }}</p>
    } @else {
      <p>Loading...</p>
    }
  `,
})
export class ProductDetailComponent implements OnInit {
  @Input({ required: true }) productId!: number;

  product: Product | null = null;
  private productService = inject(ProductService);

  ngOnInit(): void {
    // Safe to use this.productId here — it has been set by Angular
    this.productService.getById(this.productId).subscribe(p => {
      this.product = p;
    });
  }
}
Warning
Never make HTTP requests or access @Input() values in the constructor. The constructor runs before inputs are set. Use ngOnInit for initialization code.
ngOnChanges — Reacting to Input Changes

ngOnChanges fires before ngOnInit and every time an @Input value changes. It receives a SimpleChanges object with the previous and current values:

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

@Component({
  selector: 'app-chart',
  standalone: true,
  template: `<canvas #chart></canvas>`,
})
export class ChartComponent implements OnChanges {
  @Input({ required: true }) data!: number[];
  @Input() color = 'blue';

  ngOnChanges(changes: SimpleChanges): void {
    console.log('Changes:', changes);
    // changes.data — { currentValue, previousValue, firstChange, isFirstChange() }
    // changes.color — { currentValue, previousValue, firstChange, isFirstChange() }

    if (changes['data']) {
      const prev = changes['data'].previousValue;
      const curr = changes['data'].currentValue;
      const isFirst = changes['data'].firstChange;

      if (!isFirst) {
        console.log(`Data changed from ${prev} to ${curr}`);
      }

      this.redrawChart();
    }
  }

  private redrawChart(): void {
    // Re-render the chart when data changes
  }
}
Note
ngOnChanges only fires for reference changes to object and array inputs — mutating an array in place will not trigger it. Assign a new array reference: this.items = [...this.items, newItem].
ngOnDestroy — Cleaning Up

ngOnDestroy fires just before Angular removes the component from the DOM. This is where you must clean up:

  • Unsubscribe from Observables (memory leak prevention)
  • Clear setTimeout / setInterval
  • Close WebSocket connections
  • Remove event listeners added with native DOM APIs

TS
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';

@Component({
  selector: 'app-live-clock',
  standalone: true,
  template: `<p>{{ currentTime }}</p>`,
})
export class LiveClockComponent implements OnInit, OnDestroy {
  currentTime = '';
  private subscription!: Subscription;
  private timerId!: ReturnType<typeof setInterval>;

  ngOnInit(): void {
    // Subscribe to an interval observable
    this.subscription = interval(1000).subscribe(() => {
      this.currentTime = new Date().toLocaleTimeString();
    });

    // Or use a native timer
    this.timerId = setInterval(() => {
      this.currentTime = new Date().toLocaleTimeString();
    }, 1000);
  }

  ngOnDestroy(): void {
    // Clean up — prevent memory leaks
    this.subscription?.unsubscribe();
    clearInterval(this.timerId);
  }
}
Tip
The modern alternative to manual unsubscription is the takeUntilDestroyed() operator (Angular 16+). It automatically unsubscribes when the component is destroyed, without needing ngOnDestroy.

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

@Component({
  selector: 'app-live-clock',
  standalone: true,
  template: `<p>{{ currentTime }}</p>`,
})
export class LiveClockComponent implements OnInit {
  currentTime = '';
  private destroyRef = inject(DestroyRef);

  ngOnInit(): void {
    interval(1000)
      .pipe(takeUntilDestroyed(this.destroyRef))  // auto-unsubscribes!
      .subscribe(() => {
        this.currentTime = new Date().toLocaleTimeString();
      });
  }
}
ngAfterViewInit — Accessing the DOM

ngAfterViewInit fires after Angular has fully initialized the component's view, including all child component views. This is the earliest safe moment to access @ViewChild references:

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

@Component({
  selector: 'app-auto-focus',
  standalone: true,
  template: `
    <input #searchInput type="text" placeholder="Search..." />
  `,
})
export class AutoFocusComponent implements AfterViewInit {
  @ViewChild('searchInput') searchInput!: ElementRef<HTMLInputElement>;

  ngAfterViewInit(): void {
    // Safe to access the DOM element here
    this.searchInput.nativeElement.focus();
  }
}
Warning
Do not access @ViewChild in ngOnInit — the view has not been initialized yet at that point. Always use ngAfterViewInit for the first access.
ngAfterContentInit — Content Projection

ngAfterContentInit fires after Angular projects external content into the component via &lt;ng-content&gt;. Use it to access @ContentChild and @ContentChildren queries:

TS
import { Component, ContentChild, AfterContentInit, ElementRef } from '@angular/core';

@Component({
  selector: 'app-panel',
  standalone: true,
  template: `
    <div class="panel">
      <div class="panel-body">
        <ng-content />   <!-- projected content goes here -->
      </div>
    </div>
  `,
})
export class PanelComponent implements AfterContentInit {
  @ContentChild('panelTitle') titleEl!: ElementRef;

  ngAfterContentInit(): void {
    // The projected content is now available
    if (this.titleEl) {
      console.log('Panel title:', this.titleEl.nativeElement.textContent);
    }
  }
}
Complete Lifecycle Example

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

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

  constructor() {
    console.log('1. constructor — inputs NOT available yet');
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log('2. ngOnChanges — input changed:', changes);
  }

  ngOnInit() {
    console.log('3. ngOnInit — first initialization, inputs available');
  }

  ngDoCheck() {
    console.log('4. ngDoCheck — custom change detection');
  }

  ngAfterContentInit() {
    console.log('5. ngAfterContentInit — ng-content initialized');
  }

  ngAfterContentChecked() {
    console.log('6. ngAfterContentChecked — ng-content checked');
  }

  ngAfterViewInit() {
    console.log('7. ngAfterViewInit — view and child views initialized');
  }

  ngAfterViewChecked() {
    console.log('8. ngAfterViewChecked — view and child views checked');
  }

  ngOnDestroy() {
    console.log('9. ngOnDestroy — cleanup before destruction');
  }
}
Choosing the Right Hook

Task

Use This Hook

Fetch data from a service

ngOnInit

React when an @Input changes after init

ngOnChanges or signal effect()

Access @ViewChild DOM elements

ngAfterViewInit

Access @ContentChild projected content

ngAfterContentInit

Unsubscribe / clean up

ngOnDestroy (or takeUntilDestroyed)

Set up initial state (no @Input needed)

Constructor or ngOnInit

Manual change detection

ngDoCheck (rarely needed)

Lifecycle Hooks with Signals

When using Angular Signals, many lifecycle hook patterns become simpler. Signals automatically track their dependencies and update consumers without manual subscription management:

TS
import { Component, Input, OnInit, signal, effect, inject } from '@angular/core';
import { DataService } from '../services/data.service';

@Component({
  selector: 'app-signal-demo',
  standalone: true,
  template: `
    <p>Count: {{ count() }}</p>
    <p>Double: {{ double() }}</p>
  `,
})
export class SignalDemoComponent implements OnInit {
  @Input() initialCount = 0;

  count = signal(0);
  // computed signals auto-update — no ngDoCheck needed
  double = signal(0);

  private dataService = inject(DataService);

  constructor() {
    // effects run whenever their signals change — no OnChanges needed
    effect(() => {
      console.log('Count changed to:', this.count());
      this.double.set(this.count() * 2);
    });
  }

  ngOnInit() {
    this.count.set(this.initialCount);
  }
}
Note
With Angular Signals, you will find yourself using ngOnChanges and ngDoCheck much less often. Signal effect() and computed() replace most reactive patterns that previously required these hooks.