Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Exploring Angular Lifecycle Hooks – OnInit

Welcome back to our blog series, Exploring Angular Lifecycle Hooks!

Let’s continue the series with one of the most widely used hooks, ngOnInit.

Available Lifecycle Hooks covered in this series:

OnInit’s primary purpose, according to the Angular Docs is to “Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component’s input properties. Called once, after the first ngOnChanges().”

First impressions are interesting because, going by the name alone, you would expect the OnInit hook to be executed first when the component is mounted. That almost is the case, but our friend OnChanges actually runs just before OnInit!

You may be asking yourself; what are some good use cases for OnInit? Why can’t I just put my initialization logic in the class constructor? Should class property assignments happen here or can they just happen at variable declaration?

In this article, we will review how to implement OnInit, common use cases for OnInit, and wrap-up with some bonus use cases for OnInit to answer all those questions.

Angular ngOnInit

OnInit is an Angular lifecycle method, that can be hooked into components and directives in Angular. By defining a specific method named ngOnInit on our class, we are telling the Angular runtime, that it should call our method at the appropriate time. This is a powerful and declarative way to add specific initialization logic near the beginning of our class lifecycle.

Implementing OnInit

As with other Angular lifecycle methods, adding the actual hook for OnInit is relatively simple.

Add OnInit after the implements keyword

The first step to implementing OnInit is to add OnInit after the implements keyword on a component or directive.

Here’s a typical component without any lifecycle hooks:

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

@Component({...})
export class MyValueComponent {}

Our first change is to import OnInit from Angular’s core and then create a contract with implements OnInit:

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

@Component({...})
export class MyValueComponent implements OnInit {}

Fun Fact Time: Technically it’s not required to implement the interface, Angular will call ngOnInit regardless, however, it’s very helpful for type-checking, and to allow other developers to quickly identify which lifecycle hooks are in use on this class.

Add the ngOnInit method to our class

Now that we have added the OnInit after implements the TypeScript intellisense will underline the class declaration in red, giving a warning that ngOnInit was not found. Let’s fix that by creating our new ngOnInit method.

Example Component Before:

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

@Component({...})
export class MyValueComponent implements OnInit {}

Example Component After:

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

@Component({...})
export class MyValueComponent implements OnInit {
  ngOnInit() {
    // initialization logic goes here
  }
}

You’ll also note that this lifecycle hook takes no arguments, unlike some of the others we’ll be covering in later articles.

Constructor vs OnInit

For a deep dive on Constructor vs OnInit checkout out our in-depth writeup here!

If you’re like me you’ve asked the question; should I place my initialization logic in the constructor or should I place it in ngOnInit? Well the answer, in short, is that we should place our initialization logic in ngOnInit. The constructor is best left up to the purpose of injecting dependencies, such as services, into our components.

Why? Well, to quote our in-depth writeup, “the JavaScript engine calls the constructor, not Angular directly. Which is why the ngOnInit (and $onInit in AngularJS) lifecycle hook was created.” In addition, “@Input() properties are available inside ngOnInit, however are undefined inside the constructor, by design”. This means we can access anything passed down into the component inside the ngOnInit hook.

Common Use Cases

Implementing OnInit was a simple two-step process. Let’s dive in and review some common use cases for OnInit. At the beginning of the article, we mentioned that Angular recommends the following: “Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component’s input properties. Called once, after the first ngOnChanges().”

Let’s explore this further through an example.

A GitHub Repository Explorer

Let’s imagine we are tasked with building a GitHub Repository Explorer.

Awesome! We can make a call to this service, get back our list of repositories and render them to the browser.

Let’s take a journey through building out this feature in a series of steps that will explore the common use cases for ngOnInit.

GitHub Service

Let’s explore the service a bit further, that has been provided to us.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings
// github.service.ts
@Injectable(..)
export class GithubService {
  getMostStarredRepos(limit: number = 5): Observable<GithubRepo[]> {}
  getRepoDetails(id: string): Observable<GithubRepoDetail> {}
}

Building Our Explorer

Let’s build scaffold out of two components, GithubReposComponent which is our parent component and GithubRepoComponent which will be our child component.

GithubReposComponent will be responsible for subscribing to the results of GithubService.getMostStarredRepos and passing the emitted GithubRepo objects down to each GithubRepoComponent to render repository information to the browser.

But, what is the best way to go about this? Shouldn’t we be able to just set a public class array property repos to the return value of getMostStarredRepos and loop over that value in our html template to render repository details for each item in the repos array?

Well, we are dealing with Observables here, so it’s not that simple.

Let’s imagine our parent, GithubReposComponent, has a bare bones structure similar to the following, having already implemented OnInit as described previously:

github-repos.component.ts
import { Component, OnInit } from '@angular/core';
import { GithubService } from './github.service';

@Component({
  template: `<app-github-repo [githubRepo]="null"></app-github-repo>`
})
export class GithubReposComponent implements OnInit {
  constructor(private githubService: GithubService) {}

  ngOnInit() {}
}

Now, let’s imagine our child, GithubRepoComponent, has a bare bones structure similar to the following:

// github-repo.component.ts
import { Component, Input } from '@angular/core';

@Component({
  template: `{{ githubRepo | json }}`
})
export class GithubRepoComponent {
  @Input() githubRepo: GithubRepo;
}

Finally, we have our detail page, GithubRepoDetailComponent component, for displaying repo details when navigated to. It is not currently wired up to pull parameters from the route, so you will notice that repoId is being set to an empty string. As we walk through ngOnInit we will fix that.

// github-repo-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { GithubService, GithubRepoDetails } from './github.service';

@Component({
  template: `
    {{ githubRepoDetails$ | async | json }}
  `
})
export class GithubRepoDetailComponent implements OnInit {
  githubRepoDetails$: Observable<GithubRepoDetails>;

  constructor(private githubService: GithubService) {}

  ngOnInit() {
    const repoId = '';
    this.githubRepoDetails$ = this.githubService.getRepoDetails(repoId);
  }
}

Let’s explore together how to wire these two components together to complete a simple Github Explorer feature as requested.

Observables and ngOnInit

One of the most common use cases for ngOnInit, is to establish observable assignments to component fields. It’s good practice to do this in the ngOnInit so that observables are initialized at a predictable time in the component lifecycle. This exercises good code manners as other developers have a common place to find component field assignments.

Continuing with our Github Repository Example, we need to subscribe to the return of the GithubService.getMostStarredRepos method so that we can iterate over the result and render a list of repositories, passing each GithubRepo object to an instance of the GithubRepoComponent child component. Remember, our GithubRepoComponent component has an input property named githubRepo.

Now, let’s proceed with creating our Observable property and wiring up the call to the service:

// github-repos.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { GithubService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of (repos$ | async)" 
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposComponent implements OnInit {
  repos$: Observable<GithubRepo[]>;
  
  constructor(private githubService: GithubService) {}

  ngOnInit() {
    this.repos$ = this.githubService.getMostStarredRepos();
  }
}

Great! Now you have learned the most common use case for ngOnInit, wiring up observables. When our app runs, the parent component template will subscribe to our service and render a list of repos to the browser.

Input Properties with OnInit

Another common use case for ngOnInit is to wire up observable fields here that depend on Input properties. This is because by the time ngOnInit fires, the Input properties are available to the component. If we were to try and access these in the constructor they would return the dreaded undefined.

Getting back to our example, one of the original requirements of the explorer was that we must give the user “the ability to limit how many repositories are returned.”

To meet that requirement, let’s add an input property to our GithubReposComponent component.

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

import { GithubService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of (repos$ | async)"
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposComponent implements OnInit {
  @Input() repoLimit: number;

  repos$: Observable<GithubRepo[]>;
  
  constructor(private githubService: GithubService) {}

  ngOnInit() {
    this.repos$ = this.githubService.getMostStarredRepos(this.repoLimit);
  }
}

Now, when the GithubReposComponent is rendered, the repos will be limited to the limit input being passed in to the parent component. This might look something like the following if GithubReposComponent were in our AppComponent:

app.component.html

<app-github-repos [repoLimit]="'5'"></app-github-repos>

Route Parameters inside OnInit

Most Angular applications employ the Router and as such, there are situations where you may need to retrieve parameters from that route. The recommended way to do so in Angular, is to subscribe to the ActivatedRoute.ParamMap. This returns an Observable of params that we can subscribe to and pull out values from the route.

Returning to our Github Repository Explorer example, one of the requirements was that the “user should have the ability to navigate to a repository detail page.”.

To meet that requirement, let’s imagine our GithubRepoComponent child component had a routerLink that routed to a new GithubRepoDetailComponent.

Let’s quickly update our GithubRepoComponent to route to the detail component:

// github-repo.component.ts
import { Component, Input } from '@angular/core';

@Component({
  template: `
  {{ githubRepo | json }}
  <a routerLink="/detail/{{ githubRepo.id }}">Details</a>
  `
})
export class GithubRepoComponent {
  @Input() githubRepo: GithubRepo;
}

Jumping over to our GithubRepoDetailComponent, let’s fix the issue of repoId being set to an empty string, and actually retrieve the id parameter from the route in our ngOnInit.

To do this we need to take the following steps:

// github-repo-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';

import { GithubService, GithubRepoDetails } from './github.service';

@Component({
  template: `
    {{ githubRepoDetails$ | async | json }}
  `
})
export class GithubRepoDetailComponent implements OnInit {
  githubRepoDetails$: Observable<GithubRepoDetails>;

  constructor(private githubService: GithubService, private route: ActivatedRoute) {}

  ngOnInit() {
    this.githubRepoDetails$ = this.route.paramMap.pipe(
      switchMap((params: ParamMap) => this.githubService.getRepoDetails(params.get('id')))
    );
  }
}

Now anytime we route to the detail page, our ngOnInit will handle setting up the Observable to pull the id parameter from the ActivatedRoute and wire-up the Observable to retrieve the details for the correct GitHub Repository.

Using ngOnInit in other ways

Manual Subscriptions in Tandem with OnDestroy

Subscribing via the async pipe in templates is the preferred method, as Angular manages subscribing and unsubscribing automagically for you!

There are some cases where you need to manually subscribe to Observables inside your component class. If you are doing so, ngOnInit is a great place to do that.

Let’s imagine the following component needs to setup a manual subscription to a keydown event and log to the console on each event.

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

@Component({...})
export class MyKeyDownComponent implements OnInit {
  ngOnInit() {
    fromEvent(document, 'keydown').subscribe(event => console.log({event}));
  }
}

Reflecting back to my previous OnDestroy article in this series, it’s considered good practice to always unsubscribe from subscriptions to prevent memory leaks.

Keeping that best practice, let’s capture the subscription in our ngOnInit so that we can unsubscribe in our ngOnDestroy:

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

@Component({...})
export class MyKeyDownComponent implements OnInit, OnDestroy {
  keyDownSub: Subscription;

  ngOnInit() {
    this.keyDownSub = fromEvent(document, 'keydown').subscribe(event => console.log({event}));
  }

  ngOnDestroy() {
    if (this.keyDownSub) {
      this.keyDownSub.unsubscribe();
    }
  }
}

Now you can see clearly, how ngOnInit can be used in tandem with ngOnDestroy to properly create and destroy subscriptions for effective prevention of memory leaks.

Asynchronous OnInit

Just as with other lifecycle methods, with ngOnInit you can add async in front of the method name to make use of the async/await syntax in modern JavaScript/TypeScript.

Reimagining our Github Repository Explorer, we can deal with services that return a Promise by awaiting the method call.

// github-repos-promise.component.ts
import { Component, OnInit, Input } from '@angular/core';

import { GithubPromiseService, GithubRepo } from './github.service';

@Component({
  template: `
    <app-github-repo 
      *ngFor="let repo of repos" 
      [githubRepo]="repo">
    </app-github-repo>`
})
export class GithubReposPromiseComponent implements OnInit {
  @Input() repoLimit: number;

  repos: GithubRepo;

  constructor(private githubPromiseService: GithubPromiseService) {}

  async ngOnInit() {
    try {
      this.repos = await this.githubPromiseService.getMostStarredRepos(this.repoLimit);
    } catch (error) {
      console.error({error});
    }    
  }
}

Reactive Forms and OnInit

On many occasions when using Reactive Forms in Angular, we need to construct complex FormGroup objects using the FormBuilder service. More often than not, our form fields rely on the data being passed in through Input properties. If we are constructing our FormGroup inside the constructor then this can be problematic as our Input properties will be undefined.

For safety and consistency, we should get in the habit of building our FormGroup inside ngOnInit.

Take, for example, the following MyFormComponent.

You will notice that buildForm is creating and initializing the form: FormGroup with a field that has defaultDescription set as the initial value.

Can you spot a potential bug?

// my-form.component.ts
import { Component, Input } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({...})
export class MyFormComponent {
  @Input() defaultDescription: string;

  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.buildForm();
  }

  buildForm() {
    this.form = this.fb.group({
      description: [this.defaultDescription]
    });
  }
}

You guessed it! Our defaultDescription may be undefined and therefore incorrectly initialize the description field on the form.

Let’s make a small tweak to the component to utilize OnInit so that we can ensure our defaultDescription Input has a value before building the form.

// my-form.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({...})
export class MyFormComponent implements OnInit {
  @Input() defaultDescription: string;

  form: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
   this.buildForm();   
  }

  buildForm() {
    this.form = this.fb.group({
      description: [this.defaultDescription]
    });
  }
}

As you can see, with just a small tweak to our component, we have corrected a potential bug. The Reactive Forms example above also follows the principle of using ngOnInit for accessing properties, I’ve seen many code examples that don’t follow it.

Conclusion

That brings us to the end of the article! Hopefully you’ve been able to glean some good advice on why and how to use OnInit logic in your applications.

It would also be wrong to get through an entire article without mentioning testing of some kind! Using the appropriate approaches described above will make your tests safer and more easy to test, for example you can invoke the ngOnInit method yourself and test the outcome.

I will leave you with a couple best practices that should be adopted:

To learn more techniques, best practices and real-world expert knowledge I’d highly recommend checking out my Angular courses - they will guide you through your journey to mastering Angular to the fullest!

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover