In Angular, components and directives go through a series of lifecycle phases from creation to destruction. Angular provides lifecycle hooks—special methods that allow developers to run code at specific stages of a component’s life. These hooks enable you to manage initialization, respond to input changes, perform cleanup, and optimize performance.
Mastering lifecycle hooks is essential for building robust, efficient, and maintainable Angular applications.
What Are Angular Lifecycle Hooks?
Angular creates, updates, and destroys components as part of the framework’s reactive architecture. Lifecycle hooks are methods that Angular automatically calls at these stages. They give developers control over component behavior at crucial points in its life cycle.
Key benefits include:
- Initialize component state (
ngOnInit
) - Respond to input property changes (
ngOnChanges
) - Perform cleanup (
ngOnDestroy
) - Interact with the component’s view (
ngAfterViewInit
) - Detect changes manually (
ngDoCheck
)
Overview of Lifecycle Hooks
Here is a comprehensive list of the main Angular lifecycle hooks:
Hook | Description |
---|---|
ngOnChanges(changes) | Called when input properties change |
ngOnInit() | Called once after the first ngOnChanges |
ngDoCheck() | Detect and act upon changes manually |
ngAfterContentInit() | After content (ng-content) has been projected |
ngAfterContentChecked() | After projected content is checked |
ngAfterViewInit() | After component’s view and child views are initialized |
ngAfterViewChecked() | After component’s view and child views are checked |
ngOnDestroy() | Cleanup just before Angular destroys the component |
For this post, we will focus on ngOnInit, ngOnChanges, and ngOnDestroy, which are the most commonly used hooks.
ngOnInit: Initialization Logic
ngOnInit
is invoked once after Angular initializes the component and sets input properties. It is ideal for:
- Fetching data from services
- Initializing variables
- Performing setup tasks that require inputs
Example: Using ngOnInit
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-profile',
template: <h1>{{ name }}</h1>
})
export class ProfileComponent implements OnInit {
name: string = '';
ngOnInit() {
// Initialize component state
this.name = 'John Doe';
console.log('ProfileComponent initialized with name:', this.name);
}
}
ngOnInit
is called once after the firstngOnChanges
.- Perfect for logic that should run once the component is ready.
ngOnChanges: Detect Input Changes
ngOnChanges
is called whenever Angular sets or updates input properties. This makes it ideal for components that react to data changes from a parent component.
Example: Using ngOnChanges
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user',
template: <p>User: {{ username }}</p>
})
export class UserComponent implements OnChanges {
@Input() username: string = '';
ngOnChanges(changes: SimpleChanges) {
if (changes['username']) {
console.log('Username changed from', changes['username'].previousValue, 'to', changes['username'].currentValue);
}
}
}
Parent Component
<app-user [username]="currentUser"></app-user>
<button (click)="changeUser()">Change User</button>
currentUser: string = 'Alice';
changeUser() {
this.currentUser = 'Bob';
}
- Every time
currentUser
changes,ngOnChanges
is triggered inUserComponent
. SimpleChanges
provides previous and current values of inputs.
ngOnDestroy: Cleanup
ngOnDestroy
is called just before Angular destroys the component. It is useful for:
- Unsubscribing from observables or event streams
- Clearing timers or intervals
- Removing event listeners
- Preventing memory leaks
Example: Using ngOnDestroy
import { Component, OnDestroy } from '@angular/core';
import { interval, Subscription } from 'rxjs';
@Component({
selector: 'app-timer',
template: <p>Timer running: {{ counter }}</p>
})
export class TimerComponent implements OnDestroy {
counter: number = 0;
private timerSubscription!: Subscription;
constructor() {
this.timerSubscription = interval(1000).subscribe(() => {
this.counter++;
});
}
ngOnDestroy() {
this.timerSubscription.unsubscribe();
console.log('TimerComponent destroyed, timer stopped.');
}
}
- Ensures that intervals or subscriptions are properly cleaned up.
- Prevents potential memory leaks, especially in large applications.
Combining Lifecycle Hooks
A component can implement multiple lifecycle hooks simultaneously:
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: <p>Value: {{ value }}</p>
})
export class LifecycleDemoComponent implements OnInit, OnChanges, OnDestroy {
@Input() value: string = '';
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges:', changes);
}
ngOnInit() {
console.log('ngOnInit: Component initialized');
}
ngOnDestroy() {
console.log('ngOnDestroy: Component cleanup');
}
}
ngOnChanges
runs beforengOnInit
on initial input binding.- Hooks allow structured control over component lifecycle events.
Lifecycle Hook Flow
Understanding the order of execution is critical:
ngOnChanges
– called first, on input changesngOnInit
– called once after first ngOnChangesngDoCheck
– detects custom changesngAfterContentInit
→ After projected contentngAfterContentChecked
→ After projected content checkedngAfterViewInit
→ After view initializationngAfterViewChecked
→ After view checkedngOnDestroy
→ Cleanup before destruction
Flowchart:
Input changes? → ngOnChanges → ngOnInit → ngDoCheck → ngAfterViewInit → ...
Destroy? → ngOnDestroy
Real-World Examples
1. Fetching Data on Initialization
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
- Ensures that API calls are made after component is ready.
2. Reacting to Input Changes
ngOnChanges(changes: SimpleChanges) {
if (changes['searchTerm']) {
this.filterResults(changes['searchTerm'].currentValue);
}
}
- Useful for search filters or dynamic updates based on parent inputs.
3. Cleaning up Resources
ngOnDestroy() {
clearInterval(this.refreshTimer);
this.socket.disconnect();
}
- Ensures timers and sockets are terminated properly.
Best Practices
- Use ngOnInit instead of constructor for initialization
- Constructor should only inject dependencies, not fetch data.
- Always unsubscribe in ngOnDestroy
- Prevents memory leaks, especially with RxJS or DOM events.
- Use ngOnChanges for reacting to input changes
- Do not use ngOnInit for dynamic input updates.
- Keep lifecycle hooks lightweight
- Avoid heavy computations; delegate to services.
- Combine with Services for State Management
- Use services to manage state across component lifecycle instead of storing it in the component.
- Logging for Debugging
- Use console logs or Angular DevTools to trace lifecycle events during development.
Advanced Concepts
1. ngDoCheck
- Detect and act on changes Angular can’t detect automatically.
ngDoCheck() {
if (this.someValue !== this.previousValue) {
console.log('Value changed!');
this.previousValue = this.someValue;
}
}
2. ngAfterViewInit
- Run logic after component and child views are initialized.
ngAfterViewInit() {
console.log('View initialized, safe to access DOM elements.');
}
3. ngAfterContentInit
- Triggered after ng-content projection. Useful for content-dependent logic.
ngAfterContentInit() {
console.log('Projected content is ready');
}
Leave a Reply