Angular is a robust framework that enables developers to build dynamic web applications using reusable components. Each component in Angular goes through a series of lifecycle stages from creation to destruction. Understanding component lifecycle hooks is essential for managing initialization, responding to changes, and performing cleanup operations. This article provides a comprehensive overview of Angular lifecycle hooks, practical usage, and real-world examples.
Introduction to Angular Lifecycle Hooks
Angular provides several lifecycle hooks that allow developers to tap into different stages of a component’s existence. Lifecycle hooks are methods that Angular automatically calls at specific points in a component or directive’s life. They allow you to perform tasks such as:
- Initializing data when a component is created.
- Reacting to changes in component input properties.
- Cleaning up resources before a component is destroyed.
- Interacting with child components or templates after view initialization.
Some of the most commonly used lifecycle hooks include:
- ngOnInit – Called once after the first
ngOnChanges
and after the component’s inputs are initialized. - ngOnChanges – Called whenever data-bound input properties change.
- ngAfterViewInit – Called after Angular fully initializes the component’s views and child views.
- ngOnDestroy – Called just before Angular destroys the component, useful for cleanup.
By using these hooks effectively, developers can write predictable and maintainable code while managing the component lifecycle efficiently.
ngOnInit
Overview
The ngOnInit
lifecycle hook is one of the most commonly used hooks. It is part of the OnInit
interface, which a component class can implement. Angular calls ngOnInit
once after the first ngOnChanges
and before the component is displayed in the view.
When to Use ngOnInit
- Initialize component data or state.
- Fetch data from services.
- Set up observables or subscriptions.
Example: Initialization with ngOnInit
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-posts',
template: `
<h2>Posts</h2>
<ul>
<li *ngFor="let post of posts">{{ post.title }}</li>
</ul>
`
})
export class PostsComponent implements OnInit {
posts: any[] = [];
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.getPosts().subscribe(data => this.posts = data);
}
}
Explanation:
The ngOnInit
hook is used to fetch posts from a service when the component is initialized. This ensures that the component’s data is ready when the view renders.
ngOnChanges
Overview
The ngOnChanges
hook is part of the OnChanges
interface. Angular calls this hook whenever an input property changes. The method receives a SimpleChanges
object containing previous and current values of inputs.
When to Use ngOnChanges
- Respond to changes in input properties.
- Recalculate values or update dependent properties when inputs change.
- Perform actions before the view is updated with new input data.
Example: Detecting Input Changes
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user',
template: `
<h3>User: {{ name }}</h3>
`
})
export class UserComponent implements OnChanges {
@Input() name: string = '';
ngOnChanges(changes: SimpleChanges): void {
if (changes['name']) {
console.log(Name changed from ${changes&#91;'name'].previousValue} to ${changes&#91;'name'].currentValue}
);
}
}
}
Explanation
Whenever the name
input property changes, ngOnChanges
logs the previous and current values. This is useful when a component needs to react dynamically to changing inputs.
ngAfterViewInit
Overview
The ngAfterViewInit
hook belongs to the AfterViewInit
interface. Angular calls this hook after the component’s view and its child views have been fully initialized.
When to Use ngAfterViewInit
- Access child components or directives via
@ViewChild
or@ViewChildren
. - Manipulate DOM elements after the view is rendered.
- Perform operations that depend on the view being fully initialized.
Example: Accessing a Child Component
import { Component, AfterViewInit, ViewChild } from '@angular/core';
@Component({
selector: 'app-child',
template: <p>Child component content</p>
})
export class ChildComponent {
display() {
console.log('Child component method called!');
}
}
@Component({
selector: 'app-parent',
template: `
<h2>Parent Component</h2>
<app-child></app-child>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild(ChildComponent) child!: ChildComponent;
ngAfterViewInit(): void {
this.child.display();
}
}
Explanation:ngAfterViewInit
ensures the child component is fully initialized before calling its methods. Trying to access the child in ngOnInit
would result in undefined behavior because the view may not yet be ready.
ngOnDestroy
Overview
The ngOnDestroy
hook is part of the OnDestroy
interface. Angular calls this hook just before destroying the component. It is commonly used to perform cleanup tasks such as unsubscribing from observables or removing event listeners.
When to Use ngOnDestroy
- Cancel subscriptions to avoid memory leaks.
- Clear timers or intervals.
- Perform any cleanup before the component is removed from the DOM.
Example: Cleanup with ngOnDestroy
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './data.service';
@Component({
selector: 'app-posts',
template: <p>Posts component loaded</p>
})
export class PostsComponent implements OnInit, OnDestroy {
private subscription!: Subscription;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.subscription = this.dataService.getPosts().subscribe(posts => console.log(posts));
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
console.log('PostsComponent destroyed and subscription cleaned up');
}
}
Explanation:
Unsubscribing in ngOnDestroy
prevents memory leaks, especially in components that subscribe to long-lived observables.
Lifecycle Hook Execution Order
Understanding the order in which Angular calls lifecycle hooks is important:
- ngOnChanges – Called when input properties change (called before ngOnInit).
- ngOnInit – Called once after the first ngOnChanges.
- ngDoCheck – Called during every change detection run (for custom checks).
- ngAfterContentInit – Called after content projected via
<ng-content>
is initialized. - ngAfterContentChecked – Called after every check of projected content.
- ngAfterViewInit – Called after component views and child views are initialized.
- ngAfterViewChecked – Called after every check of component and child views.
- ngOnDestroy – Called just before the component is destroyed.
Real-World Example: Component Lifecycle in Action
Consider a blog application that displays a list of posts and allows filtering by category:
import { Component, Input, OnInit, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
import { DataService } from './data.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-blog',
template: `
<h2>Posts in {{ category }}</h2>
<ul>
<li *ngFor="let post of filteredPosts">{{ post.title }}</li>
</ul>
`
})
export class BlogComponent implements OnInit, OnChanges, OnDestroy {
@Input() category: string = '';
posts: any[] = [];
filteredPosts: any[] = [];
private subscription!: Subscription;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.subscription = this.dataService.getPosts().subscribe(posts => {
this.posts = posts;
this.applyFilter();
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['category'] && this.posts.length > 0) {
this.applyFilter();
}
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
private applyFilter(): void {
this.filteredPosts = this.posts.filter(post => post.category === this.category);
}
}
Explanation:
- ngOnInit – Subscribes to the data service and initializes posts.
- ngOnChanges – Reacts to changes in the
category
input and updates the filtered list. - ngOnDestroy – Cleans up the subscription when the component is removed.
This demonstrates how multiple lifecycle hooks work together to manage initialization, input changes, and cleanup.
Best Practices for Lifecycle Hooks
- Use ngOnInit for Initialization – Avoid doing heavy logic in the constructor.
- Keep ngOnChanges Lightweight – Only react to input changes; avoid complex operations.
- Use ngAfterViewInit for DOM-Dependent Logic – Access child components or template elements safely.
- Always Cleanup in ngOnDestroy – Unsubscribe from observables and remove timers or event listeners.
- Avoid Logic in Constructor – Use the constructor only for dependency injection.
- Prefer Observables over setTimeout/setInterval – Observables integrate better with Angular’s reactive model and are easier to clean up.
Summary Code Example
@Component({
selector: 'app-sample',
template: <p>{{ message }}</p>
})
export class SampleComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
@Input() message: string = '';
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges', changes);
}
ngOnInit() {
console.log('ngOnInit called');
}
ngAfterViewInit() {
console.log('ngAfterViewInit called');
}
ngOnDestroy() {
console.log('ngOnDestroy called');
}
}
This simple component logs lifecycle events in order and serves as a template for understanding how Angular calls hooks during a component’s life.
Leave a Reply