Miloš Zeljko

Angular Lifecycle Hooks: A Deep Dive into Component Lifecycle

May 21, 2023 6 mins to read
Share

Introduction

One fundamental aspect of Angular, or any component-based framework, is understanding the lifecycle of components. This becomes significantly important when dealing with complex, real-world applications. Angular provides lifecycle hooks, special methods that allow developers to tap into specific moments in the application lifecycle, thereby enabling precise control over the component behavior.

Overview of Component Lifecycle

From creation to rendering, updating, and finally destruction, every stage in a component’s existence represents a lifecycle event. Understanding these events can make a world of difference regarding managing the state, handling performance, and debugging.

Angular provides lifecycle hook interfaces that correspond to these lifecycle events. Implementing these hooks provides us with visibility into when these lifecycle events occur and allows us to execute custom actions at these stages.

Component lifecycle hooks are invoked in a specific order:

  1. The sequence begins with the creation phase (constructor, ngOnChanges, ngOnInit),
  2. proceeds to the rendering phase (ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked),
  3. and finally ends with the teardown phase (ngOnDestroy).
angular lifecycle hooks

The component lifecycle begins with a sequence of transitions marked by white arrows, representing the initial stages. Post this initialization, the component enters a cyclic phase represented by red arrows. Finally, when the component is on the verge of destruction, a transition represented by a blue arrow occurs.

ngOnChanges

The ngOnChanges lifecycle hook is called when Angular sets or resets data-bound input properties. This hook is vital when we want to perform some action in response to changes in input values. The hook receives an object that holds the current and previous values of the input property that changed. The ngOnChanges hook can be handy when the data changes are complex or need to be tracked closely.

Here’s a basic example:

import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements OnChanges {
  @Input() data: any;

  ngOnChanges(changes: SimpleChanges) {
    if(changes['data'].currentValue !== changes['data'].previousValue) {
      // Handle the data change here
    }
  }
}
example.component.ts

ngOnInit

The ngOnInit hook comes into play right after the data-bound properties of a directive are initialized. It’s typically used for initialization work.

One might question why not perform the initialization work in a constructor? While possible, the constructor is for setting up the basic things like dependency injection. However, when the constructor is called, Angular hasn’t finished initializing the component’s inputs, which might be crucial for your initialization logic. Hence, it’s more appropriate to use the ngOnInit lifecycle hook.

Here’s a basic example:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements OnInit {
  constructor() {
    // ...
  }

  ngOnInit() {
    // Initialization logic here
  }
}
example.component.ts

ngAfterViewInit

The ngAfterViewInit hook is invoked when the component’s views and child views have been fully initialized. It’s often used to modify the DOM or to perform other post-render tasks.

If you need to select elements via the ViewChild or ViewChildren decorators, this is the lifecycle hook you should use because it assures that the elements are available in the component’s template.

Here’s a basic example:

import { Component, AfterViewInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements AfterViewInit {
  @ViewChild('exampleRef') exampleRef;

  ngAfterViewInit() {
    // The DOM is fully initialized here
    console.log(this.exampleRef);
  }
}
example.component.ts

ngOnDestroy

The ngOnDestroy lifecycle hook is called just before Angular destroys the component. This is where you should clean up any open connections, unsubscribe from observables, and detach event handlers to prevent memory leaks.

Here’s a basic example:

import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements OnDestroy {
  private _subscription: Subscription;

  ngOnDestroy() {
    // Clean up logic here
    this._subscription.unsubscribe();
  }
}
example.component.ts

Additional Lifecycle Hooks

ngDoCheck

The ngDoCheck hook is invoked immediately after ngOnChanges and ngOnInit in each change detection cycle. This hook allows you to implement your custom change-detection algorithms for any changes that Angular won’t or can’t detect. This hook is called frequently, so any operation inside this hook must be fast and efficient to avoid performance degradation.

Here’s a basic example:

import { Component, DoCheck } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements DoCheck {
  ngDoCheck() {
    // Custom change detection logic here
  }
}
example.component.ts

ngAfterContentInit

The ngAfterContentInit lifecycle hook is called after Angular completes the first content check cycle, i.e., it has projected the external content into the component’s view. This hook can be used to perform any initialization involving projected content.

Here’s a basic example:

import { Component, AfterContentInit } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements AfterContentInit {
  ngAfterContentInit() {
    // Initialization logic involving projected content
  }
}
example.component.ts

ngAfterContentChecked

The ngAfterContentChecked lifecycle hook is called after Angular checks the content projected into the component. It is invoked after the ngAfterContentInit and every subsequent ngDoCheck. You might use this hook to react to changes in your component’s content that Angular checks.

Here’s a basic example:

import { Component, AfterContentChecked } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements AfterContentChecked {
  ngAfterContentChecked() {
    // React to the content changes
  }
}
example.component.ts

ngAfterViewChecked

The ngAfterViewChecked hook is called after Angular performs change detection on a component’s view (and child views). It is invoked after ngAfterViewInit and then after every subsequent ngAfterContentChecked. This hook can be a perfect spot to react to changes affecting your component’s view.

Here’s a basic example:

import { Component, AfterViewChecked } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent implements AfterViewChecked {
  ngAfterViewChecked() {
    // React to the view changes
  }
}
example.component.ts

Remember, while these additional lifecycle hooks offer more granular control, they should be used judiciously. Adding unnecessary logic in these hooks can impact application performance. They should only be used when specific customization or control is needed that can’t be achieved using the primary lifecycle hooks.

Best Practices and Common Pitfalls

  • Keep logic in lifecycle hooks clean and minimal: Too much logic in hooks can affect performance negatively and make the code harder to follow.
  • Unsubscribe from observables: Not unsubscribing can cause memory leaks as the component can still be listening to them even after it has been destroyed.
  • Avoid complex operations in ngOnChanges: Complex operations in this hook can slow down the application because it runs every time an input property changes.

Conclusion

Mastering the Angular lifecycle hooks is a crucial step toward becoming an effective Angular developer. Understanding the sequence of these hooks and knowing the right place to inject certain logic in the lifecycle helps to create more efficient, robust, and manageable Angular applications. As we have seen, each hook serves a specific purpose and provides us with a degree of control over different stages of the component lifecycle. Stay curious, keep experimenting, and keep coding!

Leave a comment

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