Every component in Angular has its own lifecycle events that occurs as the component gets created, renders, changes it's property values or gets destroyed. Angular invokes certain set of methods or we call them hooks, that gets executed as soon as those lifecycle events gets fired.
Lifecycle hooks are wrapped in certain interfaces which are included in the angular core '@angular/core' library.
One thing to note that each of these interfaces includes one method whose name is same as of the interface name but it is just prefixed by "ng". For example OnInit interface has one method ngOnInit(). The following lifecycle hooks are exposed by Angular corresponding to different lifecycle events.
- ngOnChanges()
- ngOnInit()
- ngDoCheck()
- ngAfterContentInit()
- ngAfterContentChecked()
- ngAfterViewInit()
- ngAfterViewChecked()
- ngOnDestroy()
These are pretty much well documented in Angular official documentation (https://angular.io/guide/lifecycle-hooks/) but I'll try to explain them in an easy way. Let's take one by one to understand what all these are and then we will move on to how these gets invoked.
ngOnChanges()
It is invoked whenever an Input() property of a component gets changed. We know that if we are using an Input() decorator this means it is an input property, the value for which is going to be supplied by it's parent component. This will gets executed every time the Input() property value changes.
ngOnInit()
The data value initializes after the component gets constructed. After this the ngOnInit() executes. This is called one time only per component creation. This is normally used to initialize any http calls or any heavy startup operations which should not be executed in constructor function.
ngDoCheck()
Normally Angular automatically does the change detection of properties and events and updates the view or invokes any other event accordingly. But sometimes, we need to execute some functionality of keep checking certain thing on every change detection. This is where ngDoCheck() does the job for you. For example - you want to check the performance of your complex component as how many changes are happening frequently. In this case we can log things which let us know which changes and how frequently those changes are happening.
Please note that implementing this method is very costly as this gets executed on every change cycle. So do try to avoid it and use if it is really really required.
ngAfterViewInit() and ngAfterViewChecked()
These are similar to AfterViewInit() and AfterViewChecked() but the difference can be found from below examples: If we normally want to render any child component, we usually place the child selector in parent template e.g:
`<div>
<my-child></my-child>
</div>`
if we want to import some external html into our view, which may or may not be an Angular child template, then we can achieve the same through AfterContect hooks. The component have to use to import external html or another Angular component into the view.
`<div>
<ng-content></ng-content>
</div>`
ngOnDestroy()
Any clean-up logic for the component, we use to write in this hook. As soon as the component gets destroyed, this method is invoked. This is very useful and should be used to free up any global variable, temporary subscriptions or any third party initialization.
Now, we pretty much understood what these life cycle hooks are. Let's now focus on how these lifecycle hooks invoked in an Angular component lifecycle.
Here is an example - https://stackblitz.com/edit/angular-life-cycle-hooks-demo?embed=1. Here, a sample Sign up form component is created. There is one more child component called "Random" component which is a part of Sign up form component. This sign up form can be shown or hide from our main app.
Signup component
import {Component, Injectable} from '@angular/core'
@Component({
selector: 'signup',
template : `<div>
<h2>Name : </h2>
<input type='text' [(ngModel)]='name' />
<input type='text' [(ngModel)]='prefix' />
<random [salt]='prefix'
(generatePassword)='onGeneratePassword($event)'></random>
<h3>Password : </h3>
</div>`
})
export class Signup implements OnInit, OnChanges, DoCheck, AfterViewChecked, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy{
name:string = 'Angular';
prefix:string = '';
password: string = '';
constructor(){
this.name = 'Angular 2';
}
onGeneratePassword(pwd: string){
this.password = pwd;
}
ngOnInit(){
this.name = 'Angular 5';
console.log('Parent ngOnInit() called');
}
ngOnChanges(){
console.log('Parent ngOnChanges() called');
}
ngDoCheck(){
console.log('Parent ngDoCheck() called')
}
ngAfterViewChecked(){
console.log('Parent AfterViewChecked() 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');
}
}
Random password generator component:
import {Component, Injectable, Input, Output, EventEmitter} from '@angular/core'
@Component({
selector: 'random',
template: `<div>
<input type='button' (click)='random()' value='Generate Password' />
</div>`
})
export class Random implements OnInit, OnChanges, DoCheck, AfterViewChecked, AfterViewInit, AfterContentInit, AfterContentChecked, OnDestroy{
@Input() salt:string;
@Output() generatePassword: EventEmitter<string> = new EventEmitter<string>();
constructor(){
this.salt = '';
}
random(){
var t = new Date().getTime();
this.generatePassword.emit(this.salt + t.toString());
}
ngOnInit(){
console.log('Child ngOnInit() called');
}
ngOnChanges(){
console.log('Child ngOnChanges() called');
}
ngDoCheck(){
console.log('Child ngDoCheck() called');
}
ngAfterViewChecked(){
console.log('Child AfterViewChecked() 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');
}
}
As your can see, we have captured all events of child and parent component to showcase how they are getting invoked and at what sequence of component's life cycle. Here is the output on click of "Show Signup". Let's discuss this:
First and foremost, parent and child component gets constructed. The parent component initialization happens.
Just after ngOnInit() and ngDoCheck(), AfterContent is getting called. This means that any external html is first compiled into the view. After this the child component starts initializing. As discussed earlier as well that ngOnChanges() will be invoked whenever any child's Input() property is getting initialized or changed.
Now, if we closely look, Child's AfterView hooks are called first. This means the child will prepare it's complete template before handing it over to the parent. After that the Parent's AfterView hooks are called because parent now rely on the fact that it's own view is complete because it's child views were completed.
After clicking "Hide Signup", the parent component will be removed because of *ngIf. This means all it's child components will also be destroyed as well. Here is the output of this click:
This indicates that the child component clean-up will be done first and then parent component clean-up will be executed.
There is one catch left in this example, if you see that for both parent and child component AfterContentChecked and AfterViewChecked are getting called twice.
Why is that? It's for you folks, let's put your comments below to explain what is the catch here.