Angular Lifecycle Hooks

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:

  1. Initialize component state (ngOnInit)
  2. Respond to input property changes (ngOnChanges)
  3. Perform cleanup (ngOnDestroy)
  4. Interact with the component’s view (ngAfterViewInit)
  5. Detect changes manually (ngDoCheck)

Overview of Lifecycle Hooks

Here is a comprehensive list of the main Angular lifecycle hooks:

HookDescription
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 first ngOnChanges.
  • 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 in UserComponent.
  • 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: &lt;p&gt;Timer running: {{ counter }}&lt;/p&gt;
})
export class TimerComponent implements OnDestroy {
  counter: number = 0;
  private timerSubscription!: Subscription;

  constructor() {
this.timerSubscription = interval(1000).subscribe(() =&gt; {
  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: &lt;p&gt;Value: {{ value }}&lt;/p&gt;
})
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 before ngOnInit on initial input binding.
  • Hooks allow structured control over component lifecycle events.

Lifecycle Hook Flow

Understanding the order of execution is critical:

  1. ngOnChanges – called first, on input changes
  2. ngOnInit – called once after first ngOnChanges
  3. ngDoCheck – detects custom changes
  4. ngAfterContentInit → After projected content
  5. ngAfterContentChecked → After projected content checked
  6. ngAfterViewInit → After view initialization
  7. ngAfterViewChecked → After view checked
  8. ngOnDestroy → 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&#91;'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

  1. Use ngOnInit instead of constructor for initialization
    • Constructor should only inject dependencies, not fetch data.
  2. Always unsubscribe in ngOnDestroy
    • Prevents memory leaks, especially with RxJS or DOM events.
  3. Use ngOnChanges for reacting to input changes
    • Do not use ngOnInit for dynamic input updates.
  4. Keep lifecycle hooks lightweight
    • Avoid heavy computations; delegate to services.
  5. Combine with Services for State Management
    • Use services to manage state across component lifecycle instead of storing it in the component.
  6. 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');
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *