What Are Lifecycle Hooks?
Every Angular component goes through a well-defined lifecycle — from creation to destruction. Angular exposes key moments in this lifecycle through lifecycle hooks: special methods you can implement in your component class to run logic at the right time.
Understanding lifecycle hooks is essential for managing subscriptions, fetching data at the right moment, and avoiding common memory leaks.
The Complete Lifecycle Hook Sequence
Angular calls these hooks in the following order:
ngOnChangesngOnInitngDoCheckngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewCheckedngOnDestroy
The Most Important Hooks
ngOnInit
This is the most commonly used hook. It runs once after the component's input properties have been initialized. Use it to fetch data from a service, set up subscriptions, or initialize component state.
export class UserProfileComponent implements OnInit {
user: User;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUser(1).subscribe(data => this.user = data);
}
}
Why not use the constructor? The constructor is for dependency injection only. Angular hasn't processed input bindings yet at that point, so ngOnInit is the correct place for initialization logic.
ngOnChanges
Called before ngOnInit and every time an @Input() property changes. It receives a SimpleChanges object that describes what changed.
ngOnChanges(changes: SimpleChanges): void {
if (changes['userId']) {
this.loadUserData(changes['userId'].currentValue);
}
}
Use this hook when your component needs to react to parent-driven data changes.
ngAfterViewInit
Runs once after Angular has fully initialized the component's view and child views. This is the correct place to interact with child components or DOM elements referenced via @ViewChild.
@ViewChild('chartCanvas') chartCanvas: ElementRef;
ngAfterViewInit(): void {
this.renderChart(this.chartCanvas.nativeElement);
}
ngOnDestroy
Called just before Angular destroys the component. This is critical for cleanup: unsubscribe from Observables, clear timers, and detach event listeners to prevent memory leaks.
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.dataService.stream$
.pipe(takeUntil(this.destroy$))
.subscribe(data => this.data = data);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
Quick Reference Table
| Hook | When It Runs | Common Use Cases |
|---|---|---|
ngOnChanges |
On every @Input change | React to parent data updates |
ngOnInit |
Once, after first ngOnChanges | Data fetching, subscriptions |
ngAfterViewInit |
Once, after view renders | DOM access, third-party libs |
ngOnDestroy |
Before component is removed | Cleanup subscriptions, timers |
Best Practices
- Always implement the corresponding interface (e.g.,
implements OnInit) — it provides type safety and IDE autocompletion. - Keep hook logic focused — extract heavy logic into private methods or services.
- Never skip
ngOnDestroycleanup when working with long-lived Observables. - Avoid triggering side effects inside
ngDoCheck— it runs on every change detection cycle and can seriously hurt performance.