Skip to content

Angular Lifecycle Hooks Explained — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Angular Lifecycle Hooks Explained. We cover key concepts, practical examples, and best practices to help you master this topic.

Angular lifecycle hooks are methods that Angular calls at specific moments during a component's existence, from creation and change detection to destruction.

What You'll Learn

  • The complete lifecycle of an Angular component
  • When and why each hook runs
  • How to use ngOnInit, ngOnChanges, ngAfterViewInit, and ngOnDestroy
  • The order of hook execution
  • Best practices for each lifecycle hook

Why It Matters

Lifecycle hooks let you execute code at precise moments. Fetch data when the component initializes, clean up subscriptions when it destroys, and respond to input changes without manual tracking.

Real-World Use

Durga Antivirus Pro uses ngOnInit to start a threat scan when the dashboard loads, ngOnChanges to react to filter changes, and ngOnDestroy to abort pending API requests when the user navigates away.

flowchart TD
    A[Component Created] --> B[constructor]
    B --> C[ngOnChanges]
    C --> D[ngOnInit]
    D --> E[ngDoCheck]
    E --> F[ngAfterContentInit]
    F --> G[ngAfterContentChecked]
    G --> H[ngAfterViewInit]
    H --> I[ngAfterViewChecked]
    I -->|Destroy| J[ngOnDestroy]
    style A fill:#f97316,color:#fff

Constructor vs OnInit

The constructor runs first, but its dependencies may not be fully ready. Use ngOnInit for initialization logic:

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

@Component({
  selector: "app-data-loader",
  standalone: true,
  template: `<p *ngIf="data">{{ data | json }}</p>`
})
export class DataLoaderComponent implements OnInit {
  data: any = null;

  constructor() {
    console.log("Constructor called - component is being created");
    // Do NOT fetch data here - inputs may not be set yet
  }

  ngOnInit() {
    console.log("ngOnInit called - component is ready");
    // Safe to fetch data, access @Input values, etc.
    this.data = { message: "Loaded after initialization" };
  }
}

Expected output: Console shows "Constructor called" first, then "ngOnInit called". The template renders the data object.

Angular sets the component's input properties before calling ngOnInit, making it the earliest safe point to access inputs and start async operations.

ngOnChanges

Called whenever an @Input property changes:

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

@Component({
  selector: "app-user-display",
  standalone: true,
  template: `
    <p>Name: {{ name }}</p>
    <p>Age: {{ age }}</p>
    <p *ngIf="previousName">Changed from {{ previousName }} to {{ name }}</p>
  `
})
export class UserDisplayComponent implements OnChanges {
  @Input() name = "";
  @Input() age = 0;
  previousName = "";

  ngOnChanges(changes: SimpleChanges) {
    if (changes["name"]) {
      console.log("Name changed:", changes["name"]);
      this.previousName = changes["name"].previousValue || "";
    }
    if (changes["age"]) {
      console.log("Age changed from", changes["age"].previousValue, "to", changes["age"].currentValue);
    }
  }
}

Expected output: When the parent changes the name input, the component logs the change and displays the previous name. SimpleChanges provides previousValue, currentValue, and firstChange for each changed input.

ngOnChanges runs before ngOnInit on the first change detection. It only fires when input bindings change, not on internal state updates.

ngAfterViewInit and ngAfterContentInit

These hooks fire after child components and projected content are ready:

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

@Component({
  selector: "app-video-player",
  standalone: true,
  template: `
    <video #player width="400" controls>
      <source src="sample.mp4" type="video/mp4" />
    </video>
  `
})
export class VideoPlayerComponent implements AfterViewInit {
  @ViewChild("player") videoPlayer!: ElementRef<HTMLVideoElement>;

  ngAfterViewInit() {
    console.log("Video element:", this.videoPlayer.nativeElement);
    this.videoPlayer.nativeElement.volume = 0.5;
    // Safe to interact with the DOM now
  }
}

Expected output: The video element is fully rendered when ngAfterViewInit fires. The volume set here takes effect immediately.

ngAfterViewInit is the ideal place to interact with child components or DOM elements accessed via @ViewChild. The view's children are guaranteed to be initialized at this point.

ngOnDestroy

Clean up resources when a component is destroyed:

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

@Component({
  selector: "app-live-timer",
  standalone: true,
  template: `<p>Timer: {{ count }}</p>`
})
export class LiveTimerComponent implements OnInit, OnDestroy {
  count = 0;
  private subscription: Subscription | null = null;

  ngOnInit() {
    this.subscription = interval(1000).subscribe(value => {
      this.count = value;
    });
  }

  ngOnDestroy() {
    console.log("Destroying timer - cleaning up subscription");
    this.subscription?.unsubscribe();
  }
}

Expected output: A timer that counts every second. When the component is removed (navigating away or hiding), the subscription is cleaned up and the timer stops.

Failing to unsubscribe in ngOnDestroy causes memory leaks. Angular provides the async pipe as an alternative that handles subscription automatically.

Hook Execution Order

Understanding the order helps avoid timing bugs:

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

  constructor() { console.log("0. Constructor"); }
  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"); }
}

Expected output: The console shows hooks in order 0 through 8. ngDoCheck, ngAfterContentChecked, and ngAfterViewChecked fire on every change detection cycle.

The constructor is called first. ngOnChanges fires when inputs change. ngOnInit fires once. The check hooks fire on every change detection. ngOnDestroy fires once when the component is removed.

Common Mistakes

  1. Fetching data in the constructor — Inputs are not set yet, and the component is not initialized. Always use ngOnInit for initialization logic.

  2. Mutating @Inputs in ngOnChanges — Changing an input inside its own change handler causes infinite loops. Use defensive copies or local state.

  3. Accessing ViewChild before AfterViewInit@ViewChild is undefined until ngAfterViewInit. Access it in ngOnInit and it will be null.

  4. Forgetting to implement the interface — Angular does not require interface implementation for hooks to work, but TypeScript will help catch typos if you implement the interface.

  5. Heavy computation in DoCheckngDoCheck fires on every change detection cycle, which is very frequent. Keep it lightweight or avoid it.

Practice Questions

  1. What is the difference between ngOnInit and constructor? The constructor creates the class. ngOnInit runs after Angular has initialized the component's inputs and resolved dependencies.

  2. When should you use ngOnDestroy? For cleanup: unsubscribe from observables, clear timers, detach event listeners, abort HTTP requests.

  3. What does SimpleChanges contain? An object where each key is an @Input property name, and each value has previousValue, currentValue, and firstChange properties.

  4. What is the last hook called before the component is destroyed? ngOnDestroy.

  5. Why should you not access ViewChild in ngOnInit? ViewChild is not initialized until Angular has composed the view, which happens after ngOnInit and before ngAfterViewInit.

Challenge

Build a CountdownTimerComponent with an @Input() seconds (default 30). Use ngOnInit to start an interval, ngOnChanges to reset the timer when seconds input changes, and ngOnDestroy to clear the interval. Display the remaining seconds and a "Time's Up!" message when it reaches zero.

FAQ

Does Angular require implementing the interface for hooks?

No, Angular calls the methods by name regardless of interface implementation, but interfaces catch typos.

What is the difference between AfterContentInit and AfterViewInit?

AfterContentInit fires after projected content (ng-content) initializes. AfterViewInit fires after the component's own view initializes.

Can I skip OnInit and use the constructor instead?

Avoid it. Inputs are not set in the constructor, and testing is harder. Always use ngOnInit for initialization.

How often does ngDoCheck run?

On every change detection cycle, which can be many times per second. Keep it performant.

What happens if an error is thrown in a lifecycle hook?

Angular logs the error and continues. The component may be in an inconsistent state. Use error handling in critical hooks.

Mini Project

Build a DataDashboardComponent that fetches data on init, shows a loading state, and refreshes when inputs change. Use ngOnInit for the initial fetch, ngOnChanges to re-fetch when filter inputs change, and ngOnDestroy to abort pending requests. Track and display the component's lifecycle events in a debug panel.

What's Next

Continue with change detection and zone.js:

Angular Change Detection, Angular Zone.js, Angular Components

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro