Introduction To Dependency Injection And Services In Angular 2 (by Pankaj Kumar Choudhary)

Dependency Injection is the heart of Angular applications. Dependency Injection is a “25 dollar” term used for  “10 cents” work. Sometimes, it is a very confusing term for beginner and intermediate developers. In this article, we will learn what Dependency Injection is and how it works.

Dependency Injection is a combination of two terms, those are “Dependency” and “Injections”. Dependency is an object or service that can be used in any another object and Injection is a process of passing the dependency to a dependent object. It creates a new instance of class along with its required dependencies.

In Angular 1, we can inject dependency in different ways and at different place of the application.

At Controller Level 

  1. app.controller(‘newController’,function($scope,$http,mathService)])  

At Directive Level 

  1. app.directive('newDirective', function($http, mathService) {}   

In Angular 2, we inject dependency via constructor of the class.

Example 

  1. import { Injectable,Inject } from '@angular/core';  
  2. import {LoggerService} from './logger.service';  
  3.   
  4.   
  5. export class NumListService {  
  6.   
  7. list:number[]=[];  
  8.   
  9.   constructor(@Inject(LoggerService) private loggerServcie) {  
  10.      
  11.    }  
  12.   
  13.   public addNum(num:number){  
  14.     this.list.push(num);  
  15.     this.loggerServcie.log("addNum");  
  16.   }  
  17.   
  18.   public getList(){  
  19.     this.loggerServcie.log("getList");  
  20.     return this.list;  
  21.   }  
  22.   
  23. }   

In the above code, we have injected the “NumListServcie” dependency in constructor and made the “numList” object of “NumListServcie” type.

Dependency Injection in Angular 2

Dependency Injection in Angular 2 consists of three aspects.

Injector

The injector object is used to create an instance of a dependency. The injector is a mechanism that provides a method using which a dependency is instantiated. To create a dependency, an injector looks for a provider. Let’s take an example. 

  1. import { ReflectiveInjector } from '@angular/core';  
  2. var Injector=ReflectiveInjector.resolveAndCreate([MathematicsService,AnotherSrvcie]);  
  3. var Math=Injector.get(MathematicsService);   

In the above code, we implement “ReflectiveInjector” from Angular, that provides some APIs to create an Injector.

You can see several methods of “ReflectiveInjector” that are used to create an Injector in different ways. We used the “resloveAndCreate()” method to create an injector. This method is actually a factory method that creates an injector and takes a list of providers.

You can find more about the “resolveAndCreate” method in definition of this method.

When we need the “MathematicsService”, we tell the Injector to get it for us.

Note – During the development, we don’t need to define the Injector. Angular application manages all of this internally. In the above example, we took an example of how Injector works in Angular.

Provider

A provider is a mechanism using which we register our dependency to be injected. In Angular, we can register our dependency at two levels.

At App Level

Registering the dependency at app level creates the injected dependency singleton. 

  1. @NgModule({  
  2.   declarations: [  
  3.     AppComponent,  
  4.     CustomDirective,  
  5.     NgNifDirective,  
  6.     Comp1Component,  
  7.     Comp2Component   
  8.   ],  
  9.   imports: [  
  10.     BrowserModule,  
  11.     FormsModule,  
  12.     HttpModule,  
  13.     CommonModule  
  14.   ],  
  15. //Register Dependency At App Level  
  16.    providers:[NumListService,LoggerService],  
  17.   bootstrap: [AppComponent]  
  18. })  
  19. export class AppModule { }   

At Component Level

You can also register the dependency at component level. There a new instance of the dependency will create. 

  1. import { Component } from '@angular/core';  
  2. import {NumListService} from '../num-list.service'  
  3.   
  4. @Component({  
  5.   selector: 'app-comp1',  
  6.   templateUrl: './comp1.component.html',  
  7.   //Inject dependency at component Level  
  8.   providers:[NumListService],  
  9.   styleUrls: ['./comp1.component.css']  
  10. })  
  11. export class Comp1Component  {  
  12.   
  13. constructor(private numList:NumListService) { }  
  14. list:number[]=[];  
  15.   
  16.   
  17. public AddNum(value:number){  
  18. this.numList.addNum(value);  
  19. this.list= this.numList.getList();  
  20. }  
  21.     
  22.   
  23. }   

Dependency

The dependency that we are injecting in a constructor is an object of the class that we want to use in another class. 

  1. import { Component } from '@angular/core';  
  2. import {NumListService} from '../num-list.service'  
  3.   
  4. @Component({  
  5.   selector: 'app-comp1',  
  6.   templateUrl: './comp1.component.html',  
  7.   //Inject dependency at component Level  
  8.   providers:[NumListService],  
  9.   styleUrls: ['./comp1.component.css']  
  10. })  
  11. export class Comp1Component  {  
  12.   
  13. //Implement dependency in constructor  
  14. constructor(private numList:NumListService) { }  
  15. list:number[]=[];  
  16.   
  17.   
  18. public AddNum(value:number){  
  19. this.numList.addNum(value);  
  20. this.list= this.numList.getList();  
  21. }  
  22.     
  23.   
  24. }   

What’s a Service in Angular 2

Services are a piece of code that are used to perform a specific task, a service can contain a value or function or combinations of both. Services are injected into application using dependency injection mechanism. Services prevent us from writing the same code at multiple sections of our application. The best solution is to write services are inject in application where we need it. Services provide, store, and interact with data, and a communication channel b/w classes. Service is a mechanism used to share the functionality b/w the components.

Create a service

Now, open your command line interface and write this “ng generate service Mathematics” command and press enter. This command will create a “mathematics” services in your project.

Now, open your “mathematics.service.ts” file you will get the below code. Here, “@Injectable” is metadata that defines this class as injectable instance (servcie). The Injectable decorator makes our service injectable to a component or directives and we can also create instance of this services in any other service. 

  1. import { Component } from '@angular/core';  
  2. import {NumListService} from '../num-list.service'  
  3.   
  4. @Component({  
  5.   selector: 'app-comp1',  
  6.   templateUrl: './comp1.component.html',  
  7.   //Inject dependency at component Level  
  8.   providers:[NumListService],  
  9.   styleUrls: ['./comp1.component.css']  
  10. })  
  11. export class Comp1Component  {  
  12.   
  13. //Implement dependency in constructor  
  14. constructor(private numList:NumListService) { }  
  15. list:number[]=[];  
  16.   
  17.   
  18. public AddNum(value:number){  
  19. this.numList.addNum(value);  
  20. this.list= this.numList.getList();  
  21. }  
  22.     
  23.   
  24. }   

Now, we add, multiply, subtract, and divide method in this service.

Code 

  1. import { Injectable } from '@angular/core';  
  2.   
  3. @Injectable()  
  4. export class MathematicsService {  
  5.   
  6.   constructor() { }  
  7.   
  8. public   Sum( fValue:number, sValue:number){  
  9.   
  10.     return  parseInt(fValue.toString())+parseInt(sValue.toString());  
  11.   }  
  12.   public   Sub( fValue:number, sValue:number){  
  13.     return  fValue-sValue;  
  14.   }  
  15.   public   Mul( fValue:number, sValue:number){  
  16.     return  fValue*sValue;  
  17.   }  
  18.   public   Div( fValue:number, sValue:number){  
  19.     return  fValue/sValue;  
  20.   }  
  21.   
  22. }   

Add the below code in your component’ template file.

  1.   
  2.   

  3.      {{name}}   
  4.   

  5. Fvalue: 
      
  6. Lvalue: 
      
  7.  
      
  8.  
      
  9.  
      
  10.  
      
  11. {{data}}  

  

In this code, we created two textboxes and bound “fValue” and “lValue” models with these. We also created four buttons and on the click event of these buttons, we are calling Sum, Sub, Mul, and Div functions respectively, that are defined in component’s class.

App.component.ts

  1. import { Component,Input,Output,EventEmitter } from '@angular/core';  
  2. import {MathematicsService} from './mathematics.service';  
  3. @Component({  
  4.   selector: '[app-root]',  
  5.   templateUrl: './app.component.html',  
  6.   styleUrls: ['./app.component.css']  
  7. })  
  8.   export class AppComponent {  
  9.   name:String = 'This is servcie example';  
  10.   data:string;  
  11.   
  12.   MathServcie:MathematicsService=new MathematicsService();  
  13.   Sum(fValue:number,lvalue:number){   
  14.     this.data=String(this.MathServcie.Sum(fValue,lvalue));  
  15.   }  
  16.   Sub(fValue:number,lvalue:number){   
  17.     this.data=String(this.MathServcie.Sub(fValue,lvalue));  
  18.   }  
  19.   Mul(fValue:number,lvalue:number){   
  20.     this.data=String(this.MathServcie.Mul(fValue,lvalue));  
  21.   }  
  22.   Div(fValue:number,lvalue:number){   
  23.     this.data=String(this.MathServcie.Div(fValue,lvalue));  
  24.   }  

In component, we imported the “MathematicsService” service and created Sum, Sub, Mul and Div function. In these functions, we are calling the Sum,Sub,Mul and Div functions of the “MathematicsService” service and result retrieved by these function is copied into “data” variable.

Output

Code of component’ template will render the below code in browser.

Now, insert some value into fvalue and Lvalue textbox and click on any button. When we click on any button, a function of component will call and this function internally call another function of “MathematicsService” service and we get the result.

Single VS Multiple Instance of Services

In Angular, we can create single or multiple instance of services depend upon the place where we register the dependency for example if we have multiple components and we register the dependency(service in our case) at component level then two instance of service will create or if we register our dependency at root level in @ngModel then only single instance of the service will be created.

Let’s take an example for better understanding.

Multiple Instance

Now, we will create a “NumListService” service. This Service will add a new number in list and return the list to required object.

NumListServcie 

  1. import { Injectable } from '@angular/core';  
  2.   
  3. @Injectable()  
  4. export class NumListService {  
  5.   
  6. list:number[]=[];  
  7.   constructor() { }  
  8.   
  9.   public addNum(num:number){  
  10.     this.list.push(num);  
  11.   }  
  12.   
  13.   public getList(){  
  14.     return this.list;  
  15.   }  
  16.   
  17. }   

We have created “addNum” method that takes an number and add this number in list. getList() method return this the latest data of the list.

Now, we will create a new component and named as “comp1”. Below is the template and component of Comp1.

Comp1.component.ts 

  1. import { Component } from '@angular/core';  
  2. import {NumListService} from '../num-list.service'  
  3.   
  4. @Component({  
  5.   selector: 'app-comp1',  
  6.   templateUrl: './comp1.component.html',  
  7.   providers:[NumListService],  
  8.   styleUrls: ['./comp1.component.css']  
  9. })  
  10. export class Comp1Component  {  
  11.   
  12. constructor(private numList:NumListService) { }  
  13. list:number[]=[];  
  14.   
  15.   
  16. public AddNum(value:number){  
  17. this.numList.addNum(value);  
  18. this.list= this.numList.getList();  
  19. }  
  20.     
  21.   
  22. }  

Comp1.component.html

  1. <p>  
  2.   comp1 works!  
  3. <  
  4. <input type="text"  [(ngModel)]="value" />    
  5. <input type="button" value="Add" (click)=AddNum(value) />  
  6. <br/>  
  7.   
  8. <br/>  
  9. <ul>  
  10.   <li *ngFor="let data of list">{{data}}</li>  
  11. </ul>   

In template of the component, we have created a textbox and button. On click event of the button, we get the value of “value” model that is bind to textbox and pass as a parameter of “Addnum” method. In “Addnum” method we are calling the “AddNum” method of the “NumListService” and also getting the new updated list from “NumListService”. In “Comp1” component we inject “NumListServcie” dependency at component level.

Now, we will create another component and makes the component and template same as “Comp1” component.

Comp2.component.html

  1. <p>  
  2.   comp2 works!  
  3. </p>  
  4. <input type="text"  [(ngModel)]="value" />    
  5. <input type="button" value="Add" (click)=AddNum(value) />  
  6. <br/>  
  7.   
  8. <br/>  
  9. <ul>  
  10.   <li *ngFor="let data of list">{{data}}</li>  
  11. </ul>   

Comp2.component.ts 

  1. import { Component } from '@angular/core';  
  2.  import {NumListService} from '../num-list.service'  
  3.   
  4. @Component({  
  5.   selector: 'app-comp2',  
  6.   templateUrl: './comp2.component.html',  
  7.   providers:[NumListService],  
  8.   styleUrls: ['./comp2.component.css']  
  9. })  
  10. export class Comp2Component {  
  11.   
  12.   constructor(private numList:NumListService) { }  
  13.   
  14.   list:number[]=[];  
  15.   
  16.   
  17. public AddNum(value:number){  
  18. this.numList.addNum(value);  
  19. this.list= this.numList.getList();  
  20. }  
  21.   
  22. }   

Now, both components are ready. We will use these components in “app” component that is the parent component of both. 

  1. <app-comp1></app-comp1>  
  2. <br/>  
  3. <app-comp2></app-comp2>   

When you open or refresh the project you will get the below output in your browser screen.

Now, add some values for comp1 and comp2. When you add any values for Comp1 it will not reflect in Comp2 output and also vice versa.

The reason is that we register our dependency (service) at component level, it means when we register the service using provider at component level a new instance of the service is always created that will be completely unaware of the instance of the other components.

Single Instance of Service

Instead of component level if we register the service at root level in “App.module” then it will provide a single instance for all the components. So remove the service from component level and add at the root level of application. 

  1. @NgModule({  
  2.   declarations: [  
  3.     AppComponent,  
  4.     CustomDirective,  
  5.     NgNifDirective,  
  6.     Comp1Component,  
  7.     Comp2Component   
  8.   ],  
  9.   imports: [  
  10.     BrowserModule,  
  11.     FormsModule,  
  12.     HttpModule,  
  13.     CommonModule  
  14.   ],  
  15.   //Register Dependency At App Level  
  16.    providers:[NumListService,LoggerService],  
  17.   bootstrap: [AppComponent]  
  18. })  
  19. export class AppModule { }   

After making all the above changes now if you add a value for “Comp1” it will also show in “Comp2” list and also vice versa. The reason is that now our service is providing a single instance for the both components.

Nested Services

Let’s take an example of nested services. We create a new service and use this service in already existing services. Use “ng generate service logger” command to create logger service.

Below is the code of logger service, 

  1. import { Injectable } from '@angular/core';  
  2.   
  3. @Injectable()  
  4. export class LoggerService {  
  5.   
  6.   constructor() { }  
  7.   
  8.   log(name:string){  
  9.     console.log("This is "+name+" Method");  
  10.   }  
  11.   
  12. }   

In logger service, we add the “log” method that will display the name of calling method. Now we need to use this service in another service so register the logger service at root level of the application. 

  1. import { BrowserModule } from '@angular/platform-browser';  
  2. import { NgModule } from '@angular/core';  
  3. import { FormsModule } from '@angular/forms';  
  4. import { HttpModule } from '@angular/http';  
  5. import { CommonModule } from '@angular/common';  
  6. import { AppComponent } from './app.component';  
  7. import { CustomDirective } from './custom.directive';  
  8. import { NgNifDirective } from './ng-nif.directive';  
  9. import { Comp1Component } from './comp1/comp1.component';  
  10. import { Comp2Component } from './comp2/comp2.component';  
  11. import {NumListService} from './num-list.service';  
  12. import {LoggerService} from './logger.service'  
  13. @NgModule({  
  14.   declarations: [  
  15.     AppComponent,  
  16.     CustomDirective,  
  17.     NgNifDirective,  
  18.     Comp1Component,  
  19.     Comp2Component   
  20.   ],  
  21.   imports: [  
  22.     BrowserModule,  
  23.     FormsModule,  
  24.     HttpModule,  
  25.     CommonModule  
  26.   ],  
  27.   //Register Dependency At App Level  
  28.    providers:[NumListService,LoggerService],  
  29.   bootstrap: [AppComponent]  
  30. })  
  31. export class AppModule { }   

Now inject logger service in constructor of the “num-list” service and call the “log” method of logger service. 

  1. import { Injectable } from '@angular/core';  
  2. import {LoggerService} from './logger.service';  
  3.   
  4.   
  5. export class NumListService {  
  6.   
  7. list:number[]=[];  
  8.   constructor(private loggerServcie:LoggerService) { }  
  9.   
  10.   public addNum(num:number){  
  11.     this.list.push(num);  
  12.     this.loggerServcie.log("addNum");  
  13.   }  
  14.   
  15.   public getList(){  
  16.     this.loggerServcie.log("getList");  
  17.     return this.list;  
  18.   }  
  19.   
  20. }   

After making all above changes now refresh the page, when page will reload we get the following error in console.

To resolve this error, now add the “@injectabel()” metadata in ”num-list” service. Reason is that in Angular 2 if we are using any injectable object in a service then we need to define the “injectable” meta data in host service class. So add the “Injectable” meta data in “num-list” service. 

  1. import { Injectable,Inject } from '@angular/core';  
  2. import {LoggerService} from './logger.service';  
  3.   
  4. @Injectable()  
  5. export class NumListService {  
  6.   
  7. list:number[]=[];  
  8.   
  9.   constructor( private loggerServcie) {  
  10.      
  11.    }  
  12.   
  13.   public addNum(num:number){  
  14.     this.list.push(num);  
  15.     this.loggerServcie.log("addNum");  
  16.   }  
  17.   
  18.   public getList(){  
  19.     this.loggerServcie.log("getList");  
  20.     return this.list;  
  21.   }  
  22.   
  23. }   

Now, everything works fine and we get messages in web console.

Use @Inject instead of @Injectable

In previous examples we used the @Injectable decorator to allow “num-list” class to use an injectable service. We have another way of using it so  that we can achieve same goal. Now we discuss the “@inject()” decorator, @Inject() is a manual mechanism for letting Angular know that a parameter must be injected. You can use this as below. 

  1. import { Injectable,Inject } from '@angular/core';  
  2. import {LoggerService} from './logger.service';  
  3.   
  4.       
  5. export class NumListService {  
  6.   
  7. list:number[]=[];  
  8.   
  9.   constructor(@Inject(LoggerService) private loggerServcie) {  
  10.      
  11.    }  
  12.   
  13.   public addNum(num:number){  
  14.     this.list.push(num);  
  15.     this.loggerServcie.log("addNum");  
  16.   }  
  17.   
  18.   public getList(){  
  19.     this.loggerServcie.log("getList");  
  20.     return this.list;  
  21.   }  
  22.   
  23. }   

In the above code, we told Angular to inject the “loggerServcie” for this service. So now, we don’t need to define the “@Injectable” at class level.

Conclusion

In this article, we learned about the Dependency Inject and Services in Angular 2. In the next article we cover another topic in Angular 2.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

TOP