Angular is one of the most popular front-end frameworks for building component-based robust web applications. A component in an Angular app has a lifecycle that goes through different phases, starting from its instantiation. Lifecycle hooks allow you to hook into those different phases as components are created, updated, and destroyed to gain more control over your application.
In this article, I will discuss Angular lifecycle hooks while showing when and how to use them.
What are lifecycle hooks?
Lifecycle hooks are timed methods that get activated by Angular change detection. Since Angular continuously executes change detection on data, the conditions of the current change detection cycle determine how lifecycle hooks are used. The most vital point about lifecycle hooks is the sequence in which they are used. The execution order doesn’t change, and they execute based on a predetermined sequence of load events generated by a detection cycle.
A hook, of course, only works when it satisfies specific criteria as determined by the current change detection cycle. Some resources are only accessible when a specific hook runs. You can use lifecycle hooks to hook into essential stages in the lifecycle of a component or directive to initiate new instances, initialize change detection when required, respond to changes throughout change detection, and clean up before instance destruction.
We can group these hooks into the following two categories:
- Hooks connected to the component itself or directives:
- Hooks connected to child components:
Now let’s look into these, one by one.
constructor()
A component lifecycle begins with the creation of a component. The Constructor() method is the first hook called when Angular calls new on a class to construct a component or directive, and it ensures that the class’s fields and subclasses are properly initialized.
At this time, we cannot access the input properties of the component. None of its subcomponents are created. Furthermore, projected content is also unavailable. After the component has been instantiated, Angular starts the component’s first cycle of change detection.
ngOnChanges
ngOnChanges is a lifecycle hook that is invoked once on the component’s instantiation and whenever one or more data-bound input properties change before ngOnInit() (if the component contains @Input -bound inputs). It gets triggered when Angular changes the data-bound input properties or resets them. A SimpleChanges object with the current and previous property values is passed as an optional parameter to the method.
ngOnInit
The ngOnInit hook is invoked following the first ngOnChanges(). Even when ngOnChanges() is not called when there are no template-bound inputs, ngOnInit() is still invoked. It is triggered upon the completion of initializing data-bound or input properties of a component.
After the completion of child component rendering, ngOnInit() gets invoked. Then, even after the data source of the input-bound property gets updated within the parent component, ngOnInit() isn’t invoked. So, we can call the ngOnInit() a one-time-use hook. It solely cares about initialization.
ngDoCheck
On every change detection run, ngDoCheck() is called immediately after ngOnChanges(), and on the initial run, it is called right after ngOnInit(). It detects and responds to changes that Angular alone cannot or will not detect. Following the execution of the default change detector, ngDoCheck() usually carries out change detection.
Developers can explicitly verify their data using ngDoCheck() as a custom change detector while combining it with ChangeDetectorRef. However, it is worth noting that it might lead to performance problems when mishandled.
ngAfterContentInit
Following the first ngDoCheck , the ngAfterContentInit() hook is executed. It gets triggered when the component or directive content has been initialized.
ngAfterContentChecked
ngAfterContentChecked is called following ngAfterContentInit() and each subsequent ngDoCheck(). This hook gets invoked after Angular has evaluated the content projected in the directive or component.
ngAfterViewInit
The ngAfterViewInit hook is executed after ngAfterContentChecked(). The view is always loaded immediately after the content. So, it gets triggered after Angular initializes the views and child views of the component or the view containing the directive.
Within this hook, we have access to the ElementRef of the ViewChildren after the component has been created. ngAfterViewInit awaits the resolution of either @ViewChild or @ViewChildren queries. The same view of the component helps us to query these elements. We can use this hook for cases where the loading of view content depends on other view components.
ngAfterViewChecked
The ngAfterViewChecked hook is called after the ngAfterViewInit() and every subsequent ngAfterContentChecked(). It is triggered after Angular has examined the component’s and child’s views or the view that holds the directive. Watch out for setting any variables here that are bound to the template. It will lead to unexpected errors.
ngOnDestroy
When a component’s lifecycle is over, Angular is in charge of destroying the component. However, several essential detachment steps must be finished before the component or directive can be destroyed. ngOnDestroy() is the lifecycle hook responsible for carrying out these steps.
Now, let’s see how to implement Angular lifecycle hooks through a simple example.
Here, we are going to implement a simple component named UserDetails. Additionally, the UserDetails component will have a child component named Random to generate user IDs with a user-provided prefix randomly. You may choose to display or hide this UserDetails form from the main view.
Refer to the code of the UserDetails component below.
import { Component, Injectable, OnInit, OnChanges, DoCheck, AfterViewChecked, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy } from "@angular/core";
@Component({
selector: "userdetails",
template: `
<div>
<h2>Name :</h2>
<input type="text" [(ngModel)]="name" />
<input type="text" [(ngModel)]="prefix" />
<random
[user]="prefix"
(generateUserId)="onGenerateUserId($event)"
></random>
<h3>UserID : {{ userid }}</h3>
</div>
`
})
export class UserDetails
implements
OnInit,
OnChanges,
DoCheck,
AfterViewChecked,
AfterViewInit,
AfterContentInit,
AfterContentChecked,
OnDestroy {
name: string = "Angular";
prefix: string = "";
userid: string = "";
constructor() {
this.name = "";
console.log("Parent constructor() called");
}
onGenerateUserId(uid:string) {
console.log("Generating UserID");
this.userid = uid;
}
ngOnInit() {
this.name = "John Doe";
console.log("Parent ngOnInit() called");
}
ngOnChanges() {
console.log("Parent ngOnChanges() called");
}
ngDoCheck() {
console.log("Parent ngDoCheck() called");
}
ngAfterViewChecked() {
console.log("Parent ngAfterViewChecked() called");
}
ngAfterViewInit() {
console.log("Parent ngAfterViewInit() called");
}
ngAfterContentInit() {
console.log("Parent ngAfterContentInit() called");
}
ngAfterContentChecked() {
console.log("Parent ngAfterContentChecked() called");
}
ngOnDestroy() {
console.log("Parent ngOnDestroy() called");
}
}
Refer to the code of the random ID generator component below.
import {Component, Injectable, Input, Output, EventEmitter, OnInit, OnChanges, DoCheck, AfterViewChecked, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy} from '@angular/core'
@Component({
selector: 'random',
template: `
<div>
<input type='button' (click)='random()' value='Generate UserID' />
</div>
`
})
export class Random implements OnInit, OnChanges, DoCheck, AfterViewChecked, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy{
@Input() user:string;
@Output() generateUserId: EventEmitter<string> = new EventEmitter<string>();
constructor(){
this.user = '';
console.log('Child constructor() called');
}
random(){
var t = new Date().getTime();
this.generateUserId.emit(this.user + t.toString());
console.log('random() called');
}
ngOnInit(){
console.log('Child ngOnInit() called');
}
ngOnChanges(){
console.log('Child ngOnChanges() called');
}
ngDoCheck(){
console.log('Child ngDoCheck() called');
}
ngAfterViewChecked(){
console.log('Child ngAfterViewChecked() called');
}
ngAfterViewInit(){
console.log('Child ngAfterViewInit() called');
}
ngAfterContentInit(){
console.log('Child ngAfterContentInit() called');
}
ngAfterContentChecked(){
console.log('Child ngAfterContentChecked() called');
}
ngOnDestroy(){
console.log('Child ngOnDestroy() called');
}
}
Once you click on the Show User Details button, the parent and child components’ constructors run first as the parent component’s initialization occurs.
The application’s main component code is as follows.
//app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name:string;
showUserDetails: boolean;
btnText: string;
constructor() {
this.showUserDetails = false;
this.btnText = 'Show';
}
toggleUserDetails(){
this.showUserDetails = !this.showUserDetails;
this.btnText = this.showUserDetails ? 'Hide' : 'Show';
}
}
//app.component.html
<h2>Angular Lifecycle Events Example</h2>
<div>
<input type='button' (click)='toggleUserDetails()' value='{{btnText}} User Details'>
</div>
<div *ngIf='showUserDetails'>
<userdetails></userdetails>
</div>
ngAfterContentInit and ngAfterContentChecked are invoked immediately after ngOnInit() and ngDoCheck(). This implies that all external HTML will be first rendered. Then, the child component kicks off initializing. As previously explained, whenever any child’s Input()-bound properties are initialized or updated, ngOnChanges() will be called.
Looking closely, we can see that the child component’s ngAfterViewInit and ngAfterViewChecked hooks are coming early in the execution sequence. This indicates that the child component view will be first rendered before giving it to the parent. The parent component then relies on the assumption that its own view is finished since its child’s views are done. Therefore, the parent component’s ngAfterViewInit and ngAfterViewChecked hooks are triggered next.
The following figure shows the output flow for the Show User Details button click.
Once you type a letter in the text box as a user ID prefix and then click the Generate UserID button, you will get the console output like in the following image.
So the complete flow would look like the following image.
The parent component will be destroyed when you click the Hide User Details button, depending on *ngIf. Since the parent is destroyed, each of its children will also be removed. This means that the parent components will be cleaned up after cleaning up the child components. The result is shown below.
Resources
For more details, refer to the demo application on the Angular Lifecycle Hooks GitHub repository.
Conclusion
In this article, I discussed the Angular lifecycle hooks and when, where, and why they are used in Angular app development. As our projects expand, lifecycle hooks let us precisely control how our components behave from their creation. So, it is vital to grasp the concept and practice with it to enjoy its benefits.
Thank you for reading!
Syncfusion’s Angular UI component library is the only suite you will ever need to build an app. It contains over 80 high-performance, lightweight, modular, and responsive UI components in a single package.
For existing customers, the newest Essential Studio version is available for download from the License and Downloads page. If you are not a Syncfusion customer, you can try our 30-day free trial to check out the available features. Also, check out our demos on GitHub.
If you have questions, contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!