Complete Guide: Angular lifecycle hooks

Post Editor

In this article, we will take a deeper dive into each lifecycle hook and look at how they're used in Angular.

10 min read
0 comments
post

Complete Guide: Angular lifecycle hooks

In this article, we will take a deeper dive into each lifecycle hook and look at how they're used in Angular.

post
post
10 min read
0 comments
0 comments

A component instance in Angular has a lifecycle that starts when Angular instantiates the component class and renders the component view along with its child views. The lifecycle continues with change detection, as Angular checks to see when data-bound properties change, and updates both the view and the component instance as needed. The lifecycle ends when Angular destroys the component instance and removes its rendered template from the DOM.

Directives have a similar lifecycle, as Angular creates, updates, and destroys instances in the course of execution.

Angular applications can use lifecycle hook methods to tap into key events in the lifecycle of a component or directive to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up before the deletion of instances.

Angular calls these hook methods in the following order:

  1. ngOnChanges: When an input/output binding value changes.
  2. ngOnInit: After the first ngOnChanges.
  3. ngDoCheck: Developer's custom change detection.
  4. ngAfterContentInit: After component content initialized.
  5. ngAfterContentChecked: After every check of component content.
  6. ngAfterViewInit: After a component's views are initialized.
  7. ngAfterViewChecked: After every check of a component's views.ngOnDestroy: Just before the component/directive is destroyed.

Project setup
Link to this section

We will create a new project for practical understanding using the Angular CLI:

<>Copy
ng new life-cycle-hooks

Then run the following commands to create the needed components

<>Copy
ng g c components/parent ng g c components/child

Then, add the following starter code.

<>Copy
<!-- app.component.html --> <app-parent></app-parent>
<>Copy
<!-- components/parent/parent.component.html --> <button (click)="updateUser()">Update</button> <br/> <br/> <app-child [userName]="userName"></app-child>
<>Copy
// components/parent/parent.component.ts export class ParentComponent { userName = 'Maria'; updateUser() { this.userName = 'Chris'; } }
<>Copy
// components/child/child.component.ts export class ChildComponent { @Input() userName = ''; }
<>Copy
<!-- components/child/child.component.html --> Here is the user name: {{ userName }}

With the above code, the UI looks like below:

Content imageContent image

ngOnChanges
Link to this section

This method is called once on a component's creation and then every time changes are detected in one of the component’s input properties. It receives a SimpleChanges object as a parameter, which contains information regarding which of the input properties has changed - in case we have more than one - and its current and previous values.

Note that if your component has no inputs or you use it without providing any inputs, the framework will not call ngOnChanges().

This is one of the lifecycle hooks which can come in handy in multiple use cases. It is very useful if you need to handle any specific logic in the component based on the received input property.

To demonstrate, let’s add the following to our code:

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnChanges { @Input() userName = ''; ngOnChanges(changes:SimpleChanges) { console.log('ngOnChanges triggered', changes); } }

Check the console, we should see that the console.log() is called after opening the application for the first time.

Content imageContent image

Notice that, the received changes object has three keys currentValue, previousValue, and firstChange, they work as they sound.

We could say that we want to change the userName value if it’s not the first change, or if the current value is only Chris. We could do anything here, let’s implement the second case.

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnChanges{ @Input() userName = ''; ngOnChanges(changes:SimpleChanges) { console.log('ngOnChanges triggered', changes); if (!changes['userName'].isFirstChange()){ if (changes['userName'].currentValue === "Chris") { this.userName = 'Hello ' + this.userName } else { this.userName = changes['userName'].previousValue } } } }

Here we update the userName value if it’s not the first value (because it won’t take the first input if we didn’t add this condition) and if the current value is Chris only.

Now, if we click on the Update button, we should see that the console.log() function is triggered one more time. This happens because the @Input() value changed (from Maria to Chris). If we keep clicking on the Update button we will not see anything new on the console, this happens because the @Input() is not changed.

Content imageContent image

Also, if we checked the console’s output we will see that the received object is changed like this:

Content imageContent image

ngOnInit
Link to this section

This method is called only once during the component lifecycle, after the first ngOnChanges call. ngOnInit() is still called even when ngOnChanges() is not, which is the case when there are no template-bound inputs.

This is one of the most used lifecycle hooks in Angular. Here is where you might set requests to the server to load content, maybe create a FormGroup for a form to be handled by that component, set subscriptions, and much more. It is where you can perform any initializations shortly after the component’s construction.

But what is the advantage of the ngOnInit hook if the same work (initializing a FormGroup or getting data from the server) could be done on the component’s constructor()? Well, let me explain.

Following are some of the main key points:

The constructor()

  • The default method of the class is executed when the class is instantiated and ensures proper initialization of fields in the class and its subclasses.
  • Dependency Injector (DI) in Angular, analyses the constructor parameters and when it creates a new instance by calling new MyClass() it tries to find providers that match the types of the constructor parameters, resolves them, and passes them to the constructor like new MyClass(someArg)
  • Should only be used to initialize class members but shouldn't do actual work. This is because the constructor is called before ngOnInit, at this point the component hasn’t been created yet, only the component class has been instantiated thus the dependencies are brought in, but the initialization code will not run.

The ngOnInit()

  • Is a life cycle hook called by Angular to indicate that Angular is done creating the component.
  • Should be used for all the initialization/declaration. Because at this point the component will be initialized.

To demonstrate, let’s add the following to our code

<>Copy
// components/parent/parent.component.ts export class ParentComponent implements OnInit { ... ngOnInit() { console.log('ngOnInit from the parent component'); } ... }
<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges { ... ngOnInit() { console.log('ngOnInit from the child component'); } ... }

We should see the following result on the console

Content imageContent image

It makes sense that the parent component is created first ( ngOnInit from the parent component is triggered first), then its child component. If we tried to click on the Update button, the ngOnInit will not trigger. Because, as I mentioned above, it only triggers once.

ngDoCheck
Link to this section

This hook can be interpreted as an “extension” of ngOnChanges. You can use this method to detect changes that Angular can’t or won’t detect. It is called in every change detection, immediately after the ngOnChanges and ngOnInit hooks.

This hook is costly since it is called with enormous frequency; after every change detection cycle no matter where the change occurred. Therefore, its usage should be careful to not affect the user experience.

To demonstrate, let’s add the following to our code:

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck { @Input() userName = ''; constructor() { } ... ngDoCheck() { console.log('ngDoCheck triggered'); } ... }

We should see the following result after running the application.

Content imageContent image

If we click on the Update button we should see that the ngOnChanges and the ngDoCheck are triggered

Content imageContent image

And if we keep clicking on the Update we should see that only the ngDoCheck is triggered after each click, this happens because ngDoCheck captures a change.

How does ngDoCheck capture a change if there is no change in the @Input() property?

Well, since Angular tracks object reference and we mutate the object without changing the reference Angular won’t pick up the changes and it will not run change detection for the component. Thus the new name property value will not be re-rendered in DOM. Luckily, we can use the ngDoCheck lifecycle hook to check for object mutation and notify Angular.

ngAfterContentInit
Link to this section

This method is called only once during the component’s lifecycle, after the first ngDoCheck. Within this hook, we have access for the first time to the ElementRef of the ContentChild after the component’s creation; after Angular has already projected the external content into the component’s view.

To demonstrate, let’s add the following to our code:

<>Copy
<!-- components/parent/parent.component.html --> <app-child [userName]="userName"> <div #contentWrapper>foo</div> <!-- <== Add this --> </app-child>
<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit { ... @ViewChild('wrapper') wrapper!: ElementRef; @ContentChild('contentWrapper') content!: ElementRef; ... ngAfterContentInit() { console.log('ngAfterContentInit - wrapper', this.wrapper); console.log('ngAfterContentInit - 'contentWrapper', this.content); } ... }
<>Copy
<!-- components/child/child.component.html --> <div #wrapper> <ng-content></ng-content> </div>
Content imageContent image

In the above code snippet, we projected content from the Parent component to the Child component. To learn more about content projection, check this doc.

At this point, we only have access to the projected content (contentWrapper  has the value of the projected content). Moreover, the component’s template is not initialized yet (wrapper is undefined). It will be initialized and ready to be accessed on the ngAfterViewInit hook

ngAfterContentChecked
Link to this section

This method is called once during the component’s lifecycle after ngAfterContentInit and then after every subsequent ngDoCheck. It is called after Angular has already checked the content projected into the component in the current digest loop.

To demonstrate, let’s add the following to our code:

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked { ngAfterContentChecked(): void { console.log('ngAfterContentChecked triggered'); } }
Content imageContent image

If we click on the Update button, the ngAfterContentChecked will trigger each time, as well as ngDoCheck.

Content imageContent image

ngAfterViewInit
Link to this section

This method is called only once during the component’s lifecycle, after ngAfterContentChecked. Within this hook, we have access for the first time to the ElementRef of the ViewChildren after the component’s creation; after Angular has already composed the component’s views and its child views.

This hook is useful when you need to load content on your view that depends on its view’s components; for instance when you need to set a video player or create a chart from a canvas element

To demonstrate, let’s add the following to our code:

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit { ... ngAfterViewInit(): void { console.log('ngAfterViewInit - wrapper', this.wrapper); } ... }
Content imageContent image

At this point, the component’s template is created and we have access to it.

ngAfterViewChecked
Link to this section

This method is called once after ngAfterViewInit and then after every subsequent ngAfterContentChecked. It is called after Angular has already checked the component’s views and its child views in the current digest loop.

To demonstrate, let’s add the following to our code:

<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked { @Input() userName = ''; @ViewChild('wrapper') wrapper!: ElementRef; @ContentChild('contentWrapper') content!: ElementRef; constructor() { } ... ngAfterViewChecked(): void { console.log('ngAfterViewChecked triggered'); } ... }
Content imageContent image

If we continue clicking on the Update button many times, the ngAfterViewChecked will be triggered each time, as well as, ngDoCheck and ngAfterContentChecked.

Content imageContent image

ngOnDestroy
Link to this section

Lastly, this method is called only once during the component’s lifecycle, right before Angular destroys it. Here is where you should inform the rest of your application that the component is being destroyed, in case there are any actions to be done regarding that information.

Also, it is where you should put all your cleanup logic for that component. For instance, it is where you can remove any local storage information and most importantly unsubscribe observables/detach event handlers/stop timers, etc. to avoid memory leaks.

Note that the ngOnDestroy is not called when the user refreshes the page or closes the browser. So, in case you need to handle some cleanup logic on those occasions as well, you can use the HostListener decorator, as shown below:

<>Copy
@HostListener(window:beforeunload’) ngOnDestroy() { // Insert Logic Here! }

To demonstrate, let’s add the following to our code:

<>Copy
// components/parent/parent.component.ts export class ParentComponent implements OnInit { ... isChildDestroyed = false; ... destroy() { this.isChildDestroyed = true; } ... }
<>Copy
<!-- components/parent/parent.component.html --> <button (click)="destroy()">Destroy Child</button> <app-child *ngIf="!isChildDestroyed" [userName]="userName"> <div #contentWrapper>foo</div> </app-child> <div *ngIf="isChildDestroyed">Child component is destroyed! :(</div> ...
<>Copy
// components/child/child.component.ts export class ChildComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { ... ngOnDestroy(): void { console.log('Child component is destroyed! :('); } ... }

This is our updated UI

Content imageContent image

If we click on the Destroy Child button, notice that the ngOnDestroy function will be triggered and the component will be removed from the DOM.

The below picture represents the DOM before clicking on the Destroy Child button.

Content imageContent image

And the following image represents the DOM after clicking on the Destroy Child button.

Content imageContent image

The Full Scene
Link to this section

Long story short, we can understand the lifecycle hooks by splitting the process into two steps,” first-time hooks”, and “in every change detection cycle hooks”.

Step 1: “first-time hooks”, the triggered hooks are:

  • onChanges
  • onInit
  • doCheck
  • afterContentInit
  • afterContentChecked
  • afterViewInit
  • afterViewChecked

Step 2: “in every change detection cycle hooks”, the triggered hooks are:

  • onChanges
  • doCheck
  • afterContentChecked
  • afterViewChecked

The following diagram shows exactly how Angular component/directive lifecycle works

Content imageContent image

Further reading
Link to this section

Conclusion
Link to this section

We understood Angular Lifecycle Hooks, their objectives, and when they are called and they can be very useful when creating Angular applications. Therefore it is important to know how they work and what you can achieve with them to be able to apply it whenever you might need it.

Comments (0)

Be the first to leave a comment

Share

About the author

author_image
author_image

About the author

Maria Zayed

About the author

author_image

Featured articles