LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1716 min read

Lifecycle Hooks

Run code at specific moments in a component's life — initialization, updates, and destruction — using Angular's lifecycle interfaces.

The Component Lifecycle

Every component goes through predictable stages: its inputs are set, it initializes, it potentially updates many times as inputs or state change, and it's eventually destroyed. Angular exposes each of these moments through lifecycle hook methods, implemented by adding the matching TypeScript interface to your component class.

Constructor runs
↓
ngOnChanges (if inputs are set)
↓
ngOnInit
↓
ngOnChanges (on future input changes)
↓
ngOnDestroy

ngOnInit

ngOnInit runs once, right after Angular has set the component's initial @Input() values — the standard place for initial data fetching or setup logic, rather than the constructor.

import { Component, OnInit } from '@angular/core';
@Component({ selector: 'app-posts', standalone: true, template: `...` })
export class PostsComponent implements OnInit {
posts: Post[] = [];
constructor(private postsService: PostsService) {}
ngOnInit() {
this.postsService.getPosts().subscribe(posts => {
this.posts = posts;
});
}
}
Why Not the Constructor?

The constructor should be reserved for dependency injection only — @Input() values aren't guaranteed to be set yet when the constructor runs, but they are by the time ngOnInit fires.

ngOnChanges

ngOnChanges runs whenever an @Input() property receives a new value, receiving a SimpleChanges object describing exactly what changed.

import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({ selector: 'app-product', standalone: true, template: `...` })
export class ProductComponent implements OnChanges {
@Input() productId!: string;
ngOnChanges(changes: SimpleChanges) {
if (changes['productId']) {
console.log('Product ID changed to', changes['productId'].currentValue);
}
}
}

ngOnDestroy

ngOnDestroy runs right before Angular removes a component — the place to clean up anything the component set up, like a subscription or a timer.

import { Component, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({ selector: 'app-clock', standalone: true, template: `...` })
export class ClockComponent implements OnDestroy {
private subscription: Subscription;
constructor() {
this.subscription = interval(1000).subscribe(() => console.log('tick'));
}
ngOnDestroy() {
this.subscription.unsubscribe(); // prevent a memory leak
}
}

Cleaning Up Side Effects

Always Unsubscribe

An Observable subscription that outlives its component — set up in ngOnInit or a constructor — needs an explicit unsubscribe() in ngOnDestroy, or it keeps running (and holding memory) after the component is gone. The async pipe, covered earlier, avoids this problem entirely for template-bound Observables.

Common Beginner Mistakes

Doing initial data fetching in the constructor

The constructor runs before @Input() values are set and before Angular considers the component ready — use ngOnInit for anything that depends on component state being initialized.

Forgetting to implement the matching interface (OnInit, OnDestroy, etc.)

While not strictly required for the hook to run, implementing the interface gives you compile-time verification that the method signature matches what Angular expects.

FAQs

Yes — ngAfterViewInit, ngAfterContentInit, and others exist for more specific timing needs, but ngOnInit, ngOnChanges, and ngOnDestroy cover the vast majority of everyday use cases.

Less so — signals (covered later in this course) react automatically to changes, reducing how often you need ngOnChanges specifically for reacting to input updates.

Summary

Lifecycle hooks let you hook into specific moments of a component's life, with ngOnInit and ngOnDestroy covering the vast majority of real-world needs. Next, you'll learn how to access a DOM element or child component directly with template reference variables.

Next Lesson →

Template Reference Variables & ViewChild