Angular Lifecycle Hooks Explained — Complete Guide
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
Fetching data in the constructor — Inputs are not set yet, and the component is not initialized. Always use
ngOnInitfor initialization logic.Mutating @Inputs in ngOnChanges — Changing an input inside its own change handler causes infinite loops. Use defensive copies or local state.
Accessing ViewChild before AfterViewInit —
@ViewChildisundefineduntilngAfterViewInit. Access it inngOnInitand it will be null.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.
Heavy computation in DoCheck —
ngDoCheckfires on every change detection cycle, which is very frequent. Keep it lightweight or avoid it.
Practice Questions
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.
When should you use ngOnDestroy? For cleanup: unsubscribe from observables, clear timers, detach event listeners, abort HTTP requests.
What does SimpleChanges contain? An object where each key is an @Input property name, and each value has
previousValue,currentValue, andfirstChangeproperties.What is the last hook called before the component is destroyed?
ngOnDestroy.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
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