In this article you’ll learn the different between OnInit
(the ngOnInit
lifecycle hook) versus the constructor
inside of a TypeScript class, and how to choose the right approach.
If you’re fairly new to Angular or have been working with it a while, you may be asking questions like:
“Should I inject dependencies in the constructor and also bind data?” “When should you use ngOnInit?” “What’s the different between Angular’s OnInit versus a constructor?”
Those are valid questions and I’m here to answer them. Before we begin, it’s important to realize that a constructor
is not an Angular feature, so keep this in mind.
Let’s cover constructors first then dive into ngOnInit
lifecycle hook.
What is a constructor?
The constructor
is part of a JavaScript class (or TypeScript class.
The constructor
is where we would initialize a new instance and pass data into, if required.
A constructor
is called first when a class instance is created:
class Pizza {
constructor() {
console.log('Hello world!');
}
}
// create a new instance, constructor is
// then invoked by the *JavaScript Engine*!
const pizza = new Pizza();
The JavaScript engine calls the constructor
, not Angular, so naturally it is called before any lifecycle hooks will be in Angular.
Constructors in Angular
Constructors in Angular are very important, they are our place to provide dependencies through Dependency Injection. When a class is created, Angular analyzes the constructor and looks for the providers that match the types of the constructor arguments. When it finds them it then binds them to the class.
You’ll likely have done something like this and injected another class into the constructor
:
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({...})
class PizzaComponent {
constructor(
private route: ActivatedRoute
) {}
}
This will bind ActivatedRoute
to the class, making it accessible as part of the component class via this.route
.
I like to think of a constructor
as the place to wire up your dependencies and leave it as clean as possible. As the constructor
is not called by Angular directly, it is not as easy to test.
Not only this, but Angular does not have control over when this constructor
is called, which leads us to the addition of the ngOnInit
lifecycle hook, which fires as soon as the Angular component is ready.
Lifecycle hooks are just methods on a class that describe key points in time. Some lifecycle hooks that Angular provides are;
OnInit
,OnDestroy
andOnChanges
. For the complete list of lifecycle hooks, check out the Lifecycle Hooks docs.
Before we move on, a small bit of trivia for you: when an Angular component is created the OnChanges
lifecycle hook actually is called first.
The next lifecycle hook that is called is the OnInit
hook.
With this in mind, what does the OnInit
lifecycle hook give us and when should we use it?
OnInit Lifecycle Hook
Angular components can grow in size and complexity, which often means we want to inject many dependencies and declare properties and bind observables. A constructor
would be an ideal place to inject, but not handle the wiring up.
The lifecycle hooks are invoked by Angular during the change detection cycle, whereas the
constructor
is only invoked when Angular composes the component tree.
This is where ngOnInit
comes in, which we can declare as a method after importing the OnInit
interface and using the implements
to give us some type safety:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({...})
class PizzaComponent implements OnInit {
constructor(
private route: ActivatedRoute
) {}
ngOnInit() {
// subscribe when OnInit fires
this.route.params.subscribe(params => {
// now we can do something!
});
}
}
This approach makes unit testing easier as we can control the lifecycle hook. The ngOnInit
lifecycle hook makes perfect sense as the place to create more complex logic and initializing of the component.
Here’s what technically happens when Angular instantiates our component and invokes ngOnInit
:
const instance = new PizzaComponent();
// Angular calls this when it's ready
instance.ngOnInit();
The ngOnInit
lifecycle hook is the perfect place to wire up observables, talk to services and make any requests you need.
Another very important aspect of ngOnInit
is that it is called by Angular exactly when the component’s @Input()
properties are ready:
@Component({...})
class PizzaSingleComponent implements OnInit {
@Input() pizza: Pizza;
constructor() {
console.log(this.pizza); // undefined
}
ngOnInit() {
console.log(this.pizza); // { name: 'Pepperoni', price: 399 }
}
}
This ngOnInit
phase also includes the first pass of Angular’s Change Detection, which means any @Input()
properties are available inside ngOnInit
, however are undefined
inside the constructor
, by design.
The differences are subtle, yet important. Now we’ve fully explored the constructor
role, you’re now primed to make the right decision with OnInit
. Using OnInit
gives us a guarantee that bindings are readily available and we can use them to the fullest.
🚀 Want to learn even more awesome Angular practices?
Check out my new Angular Courses if you’re serious about taking your skills to the top! There’s so much about Standalone Components and all the new best practices in the course.
Thanks for reading, happy constructing!