In this post, we’ll dive right into the code of how dependency injection works in Angular. We’ll skip the theory, so please feel free to ask anything if needed.
Before we start — When building NG apps, it’s better to share your components in a reusable collection, so you won’t have to rewrite them.
Using tools like Bit (GitHub) you can instantly share components (automatically isolated with dependencies) into a shared hub, use and develop them anywhere, sync changes and build multiple apps faster. Try it.
Suppose we have a component named app component which has a basic and simple structure as follows:
import { Component, OnInit } from "@angular/core"; @Component({ selector: "my-root", templateUrl: "app.component.html", styleUrls: ["app.component.css"] }) export class AppComponent implements OnInit { ngOnInit(): void {}
}
And we have a service class called ‘GreetingService’ with a function ‘sayHello’ that receives ‘name’ as a parameter and returns it (name) with an additional “Hello”, proceeding it.
export class GreetingService{
sayHello(name){
returnHello ${name}
;
}
}
There are two ways to use the service class in the component: first, we can manually create an instance of the service in the component (this is the wrong way and never recommended).
The other way is to let Angular create the instance of our service and pass that instance to our component internally. That is the standard and recommended way.
Now, if you run this project, you will get the error “No provider for GreetingService!”
Import {Component} from ‘@angular/core’;
Import {GreetingService} from ‘. /greetingService’;
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ]
})
export class AppComponent {
constructor(private greetingService : GreetingService){
console.log(this.greetingService.sayHello());
}
}
Angular is complaining that it did not find any provider for creating an instance of the greeting service, or it does not know how to create an instance. To let the framework know how the instance will be created, we have to pass a provider object to the provider’s property in the component decorator shown below:
In this provider object, we have many features. Let us understand them one by one.
import { Component } from ‘@angular/core’;
import {GreetingService} from ‘./greetingService’;
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ],
providers:[{}]
})
export class AppComponent {
constructor(private greetingService : GreetingService){
console.log(this.greetingService.sayHello());
}}
Use factory: this will tell the framework which factory will be used while creating the object of the service. In our case, we don’t have any factory. Let’s create one.
The factory will be a function that will be responsible for creating and returning the object of the service.
export function greetingFactory(){
return new GreetingService()
};
Or more short way
export const greetingFactory= () => new GreetingService ();
The next thing is to create a property whose value will be an Injection Token instance. While Using this property, the framework will uniquely identify our service and will inject the right instance of the service.
var greetingTokken = new InjectionToken<GreetingService>
(“GREET_TOKEN”);
In the above snippet, we are creating an instance of the InjectionToken class, and it is generic. In our case, the GreetingService instance will inject when someone asks for the injection with the name greetingToken.
For now, our code looks like this:
import { Component ,InjectionToken} from ‘@angular/core’;
import {GreetingService} from ‘./greetingService’;
export const greetingTokken = new InjectionToken<GreetingService>(“GREET_TOKEN”);
export const greetingFactory=()=> new GreetingService();
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ],
providers:[{
provide : greetingTokken,
useFactory : greetingFactory,}]
})
export class AppComponent {
constructor(private greetingService : GreetingService){
console.log(this.greetingService.sayHello());
}
name = ‘Angular’;
}
But then we also get the same error:
As in the constructor, where we are asking for the instance of our service, we have to tell it the unique string of our injection token that is greetingToken.
Let’s update our code:
export class AppComponent {
constructor(@Inject(greetingTokken) private greetingService : GreetingService){
console.log(this.greetingService.sayHello(‘Neeraj’));
}
name = ‘Angular’;
}
and now we will have the result that allows us to pass a service from Angular dependency injection successfully
Now let us assume you have some nested dependencies like this:
import{DomSanitizer} from ‘@angular/platform-browser’;
export class GreetingService{
constructor (private domSanitizer:DomSanitizer){}
sayHello(name){
returnHello ${name}
}
}
In this case, we have to give one more property to the provider’s object (that is deps), which is the array of all the dependencies:
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ],
providers:[{
provide : greetingTokken,
useFactory : greetingFactory,
deps:[DomSanitizer]}]
})
export class AppComponent {
constructor(@Inject(greetingTokken) private greetingService : GreetingService ){
console.log(this.greetingService.sayHello(‘Neeraj’));
}
name = ‘Angular’;
}
Angular does all the hard work for you. You can reduce the amount of code, and instead of passing the factory and token manually, simply ask the framework to do this for you. The ‘provide’ property, which is the injection token, will be the name of the service which we want to inject, and Angular will internally create an injection token and factory for us.
Now rather than passing the factory and injection token manually,
we will pass the other property (use-class) which tells the framework which class we need to use or inject at run time:
import { Component ,InjectionToken,Inject} from ‘@angular/core’;
import {GreetingService} from ‘./greetingService’;
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ],
providers:[{
provide : GreetingService,
useClass :GreetingService}]
})
export class AppComponent {
constructor( private greetingService : GreetingService ){
console.log(this.greetingService.sayHello(‘Neeraj’));
}
name = ‘Angular’;
}
Now our code looks much cleaner, and we can further reduce it by just passing the name of the service. Angular will create the provide object, the factory, and the injection token for us and make the instance available to us when needed.
import { Component } from ‘@angular/core’;
import {GreetingService} from ‘./greetingService’;
@Component({
selector: ‘my-app’,
templateUrl: ‘./app.component.html’,
styleUrls: [ ‘./app.component.css’ ],
providers:[GreetingService]
})
export class AppComponent {
constructor( private greetingService : GreetingService ){
console.log(this.greetingService.sayHello(‘Neeraj’));
}
name = ‘Angular’;
}
In the end, our code looks very familiar. Now in the future, whenever you create a service, you know what steps are involved in getting that instance available.
Thank for reading!
Originally published on https://blog.bitsrc.io
#angular #javascript #web-development