The Lifecycle Sequence
Angular calls lifecycle hooks in a specific order as a component is created, updated, and destroyed. Understanding this sequence helps you place logic in the right hook.
Lifecycle Order
1. constructor() — Dependency injection, initial field setup
2. ngOnChanges() — Called when @Input values change (before ngOnInit)
3. ngOnInit() — Called once after the first ngOnChanges
4. ngDoCheck() — Called during every change detection cycle
5. ngAfterContentInit() — Called once after content (ng-content) is projected
6. ngAfterContentChecked()— Called after every check of projected content
7. ngAfterViewInit() — Called once after the component's view is rendered
8. ngAfterViewChecked() — Called after every check of the component's view
9. ngOnDestroy() — Called once before the component is destroyed
ngOnInit — The Standard Initialization Hook
Use ngOnInit for logic that runs once after inputs are first set:
@Component({
selector: 'app-user-list',
template: `<p *ngFor="let user of users">{{ user.name }}</p>`,
})
export class UserListComponent implements OnInit {
@Input() userId!: number;
users: User[] = [];
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUsers(this.userId).subscribe(users => {
this.users = users;
});
}
}
ngOnChanges — Reacting to Input Changes
@Component({
selector: 'app-counter',
template: `<p>Count: {{ count }}</p>`,
})
export class CounterComponent implements OnChanges {
@Input() count!: number;
ngOnChanges(changes: SimpleChanges): void {
if (changes['count']) {
console.log('Previous:', changes['count'].previousValue);
console.log('Current:', changes['count'].currentValue);
console.log('First change:', changes['count'].firstChange);
}
}
}
ngOnDestroy — Cleanup
Always clean up subscriptions, timers, and event listeners in ngOnDestroy:
@Component({
selector: 'app-timer',
template: `<p>Elapsed: {{ seconds }}s</p>`,
})
export class TimerComponent implements OnInit, OnDestroy {
seconds = 0;
private intervalId: any;
private subscription: Subscription = new Subscription();
ngOnInit(): void {
this.intervalId = setInterval(() => this.seconds++, 1000);
this.subscription.add(
this.someService.data$.subscribe(data => {
// handle data
})
);
}
ngOnDestroy(): void {
clearInterval(this.intervalId);
this.subscription.unsubscribe();
}
}
Advanced Hooks: DoCheck, AfterView, AfterContent
Beyond the basic hooks, Angular provides additional hooks for specialized scenarios.
ngDoCheck — Custom Change Detection
Called during every change detection cycle. Use it to implement custom change detection logic:
@Component({
selector: 'app-todo-list',
template: `<p *ngFor="let todo of todos">{{ todo.text }}</p>`,
})
export class TodoListComponent implements DoCheck {
@Input() todos: Todo[] = [];
private previousCount = 0;
ngDoCheck(): void {
if (this.todos.length !== this.previousCount) {
console.log('Todo count changed:', this.previousCount, '->', this.todos.length);
this.previousCount = this.todos.length;
}
}
}
ngAfterViewInit — Accessing the DOM
Called after the component's view is fully rendered. Use it to interact with DOM elements:
@Component({
selector: 'app-chart',
template: `<canvas #chartCanvas></canvas>`,
})
export class ChartComponent implements AfterViewInit {
@ViewChild('chartCanvas') canvasRef!: ElementRef<HTMLCanvasElement>;
ngAfterViewInit(): void {
const ctx = this.canvasRef.nativeElement.getContext('2d');
// Initialize chart library
}
}
ngAfterContentInit — Projected Content
Called after ng-content is projected into the component:
@Component({
selector: 'app-card',
template: `<div class="card"><ng-content></ng-content></div>`,
})
export class CardComponent implements AfterContentInit {
@ContentChild('header') headerRef!: ElementRef;
ngAfterContentInit(): void {
console.log('Projected header:', this.headerRef?.nativeElement.textContent);
}
}
Lifecycle Hook Interfaces
Each hook has a corresponding interface for type safety:
| Hook | Interface |
|---|---|
| ngOnInit | OnInit |
| ngOnChanges | OnChanges |
| ngDoCheck | DoCheck |
| ngAfterContentInit | AfterContentInit |
| ngAfterContentChecked | AfterContentChecked |
| ngAfterViewInit | AfterViewInit |
| ngAfterViewChecked | AfterViewChecked |
| ngOnDestroy | OnDestroy |
export class MyComponent implements OnInit, OnDestroy {
ngOnInit(): void {}
ngOnDestroy(): void {}
}
Quiz
1. Which lifecycle hook is called once after the component's inputs are first set?
2. Where should you clean up subscriptions and timers?
3. What is the correct order of the first three lifecycle hooks?
Flashcards
Question
What is the lifecycle hook order?
Click to reveal answer
Answer
constructor -> ngOnChanges -> ngOnInit -> ngDoCheck -> ngAfterContentInit -> ngAfterContentChecked -> ngAfterViewInit -> ngAfterViewChecked -> ngOnDestroy
Question
When do you use ngOnInit?
Click to reveal answer
Answer
For one-time initialization logic that depends on @Input values being set. Called once after the first ngOnChanges.
Question
Where do you clean up subscriptions?
Click to reveal answer
Answer
ngOnDestroy — unsubscribe from Observables, clear intervals/timeouts, remove event listeners.
Revision Notes
Key Takeaways
- 1. ngOnInit is the standard initialization hook
- 2. ngOnDestroy is mandatory for cleanup to prevent memory leaks
- 3. ngOnChanges fires before ngOnInit and on every input change
- 4. AfterViewInit is the safe place to access DOM elements
Interview Tips
- • Be able to recite the lifecycle order
- • Explain why cleanup in ngOnDestroy prevents memory leaks
- • Mention that ngDoCheck runs on every change detection cycle
Cheat Sheet
Lifecycle Hooks Cheat Sheet
Order:
constructor -> ngOnChanges -> ngOnInit -> ngDoCheck
-> ngAfterContentInit -> ngAfterContentChecked
-> ngAfterViewInit -> ngAfterViewChecked -> ngOnDestroy
Common uses:
- ngOnInit: fetch data, setup subscriptions
- ngOnChanges: react to input changes
- ngOnDestroy: cleanup subscriptions and timers
- ngAfterViewInit: DOM manipulation
- ngDoCheck: custom change detection
Interfaces:
implements OnInit, OnChanges, OnDestroy