Angular Lifecycle Hooks
Every Angular component and directive moves through a predictable sequence of lifecycle hooks that let you run setup logic, react to input changes, and clean up before it's destroyed.
The Lifecycle Sequence
Angular calls a fixed set of methods on a component in a specific order, from creation to destruction. Implementing the corresponding interface (like OnInit) is optional but recommended, since it gives you compile-time checking that the method signature is correct.
- ngOnChanges — runs before ngOnInit and again whenever a bound @Input() value changes.
- ngOnInit — runs once, after the first ngOnChanges, when the component's inputs are set.
- ngDoCheck — runs on every change detection cycle for custom change checks.
- ngAfterContentInit / ngAfterContentChecked — run after projected content (ng-content) is initialized/checked.
- ngAfterViewInit / ngAfterViewChecked — run after the component's own view (and child views) are initialized/checked.
- ngOnDestroy — runs once, right before Angular removes the component, for cleanup.
ngOnInit for Initialization
ngOnInit is the right place for one-time setup work such as an initial data fetch. Avoid doing this in the constructor — constructors should stay lightweight and free of side effects like HTTP calls, since dependency injection is still being wired up at that point.
profile.component.ts
import { Component, OnInit, Injectable, inject, signal } from '@angular/core';
import { Observable, of } from 'rxjs';
import { bootstrapApplication } from '@angular/platform-browser';
interface User {
id: number;
name: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
getUser(id: number): Observable<User> {
return of({ id, name: 'Ada Lovelace' });
}
}
@Component({
selector: 'app-profile',
standalone: true,
template: `<p>{{ name() }}</p>`
})
export class ProfileComponent implements OnInit {
private userService = inject(UserService);
protected name = signal('Loading...');
ngOnInit(): void {
console.log('ngOnInit called');
this.userService.getUser(1).subscribe(user => this.name.set(user.name));
}
}
bootstrapApplication(ProfileComponent).catch((err) => console.error(err));
ngOnChanges for Input Reactions
ngOnChanges receives a SimpleChanges map describing which @Input() properties changed, along with their previous and current values. It fires before the values are used anywhere else in the component, making it a good spot to recompute a derived value whenever an input updates.
price-tag.component.ts
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'app-price-tag',
standalone: true,
template: `<p>Price: {{ formatted }}</p>`
})
export class PriceTagComponent implements OnChanges {
@Input() amount = 0;
@Input() currency = 'USD';
formatted = '';
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges', changes);
if (changes['amount'] || changes['currency']) {
this.formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: this.currency
}).format(this.amount);
}
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [PriceTagComponent],
template: `<app-price-tag [amount]="19.99" [currency]="'USD'"></app-price-tag>`
})
export class AppComponent {}
bootstrapApplication(AppComponent).catch((err) => console.error(err));
Cleaning Up with ngOnDestroy
ngOnDestroy runs right before Angular tears the component down — for example when a router navigates away or an @if block removes it. Anything that would otherwise keep running in the background, like an RxJS subscription or a setInterval timer, must be cancelled here to prevent memory leaks.
live-clock.component.ts
import { Component, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
import { bootstrapApplication } from '@angular/platform-browser';
@Component({
selector: 'app-live-clock',
standalone: true,
template: `<p>Seconds elapsed: {{ seconds }}</p>`
})
export class LiveClockComponent implements OnDestroy {
seconds = 0;
private sub: Subscription;
constructor() {
console.log('LiveClockComponent created');
this.sub = interval(1000).subscribe(() => this.seconds++);
}
ngOnDestroy(): void {
console.log('ngOnDestroy called - clearing subscription');
this.sub.unsubscribe();
}
}
bootstrapApplication(LiveClockComponent).catch((err) => console.error(err));
Exercise: Angular Lifecycle Hooks
What differs between a component's constructor and ngOnInit()?