Dependency Injection in Angular: Introduction and DI Techniques

Posted by: Ravi Kiran , on 6/20/2018, in Category AngularJS
Views: 3860
Abstract: Dependency Injection is an important feature in Angular to build scalable web applications. This tutorial will explore the API for DI in Angular that allows us to inject anything, thus making the code extensible and testable.

Extensibility is one of the core principles of Angular.

Every feature in Angular is built with extensibility in mind. The applications written in OOPs (Object-Oriented Programming Structures) based programming languages, use Dependency Injection (DI) to make applications extensible and testable.

Are you keeping up with new developer technologies? Advance your IT career with our Free Developer magazines covering Angular, React, .NET Core, MVC, Azure and more. Subscribe to this magazine for FREE and download all previous, current and upcoming editions.

Though JavaScript is not an Object-Oriented Programming language, it is an Object Based language. And the language features of JavaScript can be used to implement DI. AngularJS had implemented this feature and Angular (v2 onwards) continues to do so with more flexibility.

A dependency could be an external object which is used by a block of code to achieve its functionality. The dependencies can be created in the same block where they are needed, but this will make the block dependent on one type of dependency. If there is a change in the way the dependency has to be created, the block using it needs to be modified.

So, creating dependency wherever it is needed makes the code brittle and hard to maintain. It also makes it hard to test the code, as the dependencies created in it can’t be mocked.

DI solves these problems, by providing a way to inject dependencies wherever they are needed. The dependencies are created at a central location, hence reducing repetition of code. And they can be mocked while writing unit tests.

This article will explain how to use DI in Angular, as well as the different ways of creating and using dependencies in an Angular application.

How Dependency Injection (DI) works in Angular

A dependency in Angular can be a class, referred as a service or even a simple object. Angular’s Dependency Injection is based on providers, injectors, and tokens.

Every Angular module has an injector associated with it. The injector is responsible to create the dependencies and inject them when needed. Dependencies are added to the injector using the providers property of the module metadata.

Every dependency is a key-value pair, where the key is a token and the value is the object of the dependency. For Services, Angular creates the tokens by itself. For objects, we need to create the tokens.

When a dependency is injected into another block (viz., a component, directive or service), the injector resolves the object from its list of dependencies using the token. Figure 1 shows how the token, provider and the injector work together.

dependency-injection-angular

Figure 1 – DI in Angular

Similarly, providers can be added to the components as well. The dependencies registered in the providers section of a component can be used in that component, as well as in the components or directives descending this component. They can’t be used in the services or the directives and components, that don’t descend the component providing them.

Choosing between a module and the component to register the dependency is based on need.

In most cases, the dependencies are registered in the modules. The dependencies registered in the modules are registered with the application’s root injector. There will be just one instance of such dependency throughout the application.

The dependencies registered in the component have an instance as long as the component is alive and they get destroyed if the component is removed from the view.

Providing Classes

The simplest way to register and use dependencies in Angular is using TypeScript classes as services. Say, we have the following class:

import { Place } from './place.model';
import { Injectable } from '@angular/core';

@Injectable()
export class PlacesService {
  places: Place[];
  
  constructor() {
    this.places = [];
    this.places.push({ name: 'Charminar', city: 'Hyderabad', country: 'India', isVisited: true, rating: 4 });
    this.places.push({ name: 'London Bridge', city: 'London', country: 'UK', isVisited: false, rating: 4.5 });
    this.places.push({ name: 'Red Rocks', city: 'Denver', country: 'USA', isVisited: true, rating: 3 });
    this.places.push({ name: 'Taj Mahal', city: 'Agra', country: 'India', isVisited: false, rating: 5 });
    this.places.push({ name: 'Eiffel Tower', city: 'Paris', country: 'France', isVisited: true, rating: 4 });
  }
  
  toggleVisited(name: string) {
    let place: Place = this.places.find(p => p.name === name);
    place.isVisited = !place.isVisited;
  }  
}

Notice that the above class has the annotation Injectable applied. The annotation helps Angular in injecting any dependencies into the constructor of the class. Though the class PlacesService doesn’t have any dependencies, it is still useful to mark it with Injectable, as it will avoid any errors later when the dependencies are added to this class.

It has to be registered with the module as shown in the following snippet to be able to inject it into a component or elsewhere:

@NgModule({
  // Other parts of the module
  providers: [PlacesService]
})
export class AppModule {}

And then it can be used in a component by injecting in the constructor, as shown below:

@Component(...)
export class PlacesComponent {
  constructor(private placesService: PlacesService) {
  }
}

If you want to see this service working with a component, visit this plunk. Rest of the article modifies this sample to try the other techniques in DI.

Providing an Alternative Class

It is possible to override a service using another class that enhances its functionality. Say, we have the class BetterPlacesService, with the following definition:

import { Place } from './place.model';
import { Injectable } from '@angular/core';

@Injectable()
export class BetterPlacesService {
  private _places: Place[] = [];
  
  get places(){
    return this._places;
  }
  
  constructor() {
    this._places.push({ name: 'Charminar', city: 'Hyderabad', country: 'India', isVisited: true, rating: 4 });
    this._places.push({ name: 'London Bridge', city: 'London', country: 'UK', isVisited: false, rating: 4.5 });
    this._places.push({ name: 'Red Rocks', city: 'Denver', country: 'USA', isVisited: true, rating: 3 });
    this._places.push({ name: 'Taj Mahal', city: 'Agra', country: 'India', isVisited: false, rating: 5 });
    this._places.push({ name: 'Eiffel Tower', city: 'Paris', country: 'France', isVisited: true, rating: 4 });
  }
  
  toggleVisited(name: string) {
    let place: Place = this._places.find(p => p.name === name);
    place.isVisited = !place.isVisited;
  }
}

The difference between the PlacesService class and the BetterPlacesService class is, instead of providing an array of places directly from the class, it uses a getter property to expose the array. Rest of the functionality remains the same. Now we can provide this class when someone asks for PlacesService. To do so, change the providers block snippet as shown below:

providers: [PlacesService, {provide: PlacesService, useClass: BetterPlacesService}]

Now if you run the sample, you will see that BetterPlacesService is getting invoked when we use PlacesService in the PlacesComponent.

Providing a Registered Dependency

It is possible to override a dependency with another registered dependency. The example shown in the last section can be modified for this. Modify the providers array in the module as shown below:

providers: [PlacesService,
            BetterPlacesService,
            {provide: PlacesService, useExisting: BetterPlacesService}]

Make sure to register the dependency assigned to useExisting. Otherwise, it results in an error similar to the following:

unregistered-dependency-error

Figure 2 - Unregistered dependency error

Now the PlacesComponent would be still using BetterPlacesService.

Using a Value

We can use an object to replace a service. For this, we need to create an object following the blueprint of the service class. The following snippet shows an object that can be used to replace PlacesService:

export const placesObj = {
  places: [ { name: 'Charminar', city: 'Hyderabad', country: 'India', isVisited: true, rating: 4 },
    { name: 'London Bridge', city: 'London', country: 'UK', isVisited: false, rating: 4.5 },
    { name: 'Red Rocks', city: 'Denver', country: 'USA', isVisited: true, rating: 3 },
    { name: 'Taj Mahal', city: 'Agra', country: 'India', isVisited: false, rating: 5 }
    ],
  toggleVisited(name: string){
    let place: Place = this.places.find(p => p.name === name);
    place.isVisited = !place.isVisited;
  }
};

This can be added to providers using useValue, as shown below:

{provide: PlacesService, useValue: placesObj}]

Using a Factory Function

If there is a need to execute some logic before creating the service object, it can be done using a factory function. The function has to return an object similar to the one used with useValue. The following snippet shows an example of a factory function:

export function getPlacesService(){
  return new PlacesService();
}

And it can be registered as follows:

providers: [{provide: PlacesService, useFactory: getPlacesService, deps: []}]

The deps array can be used to provide any dependencies that the function needs. Here, the factory function is not accepting any inputs, so the deps array is empty.

Injecting non-class Dependencies

At times, the applications need some objects to be accessible everywhere. Though we have a way to register them using useValue, it would be an overkill to create a service and then override it with an object.

Angular provides a way to register the objects as injectables through InjectionToken.

Say we need to inject the following object in the PlacesComponent to assign max and min values for the rating that a user can provide for a place:

export class AppConstants {
  maxRating: number;
  minRating: number;
}

export const APP_CONSTANTS: AppConstants = {
  maxRating: 5,
  minRating: 1
};

The class AppConstants is created to use the object anywhere in a type-safe way. It won't be used to register the value. Now that we have the value, let's create the token for it.

The following snippet shows this:

export const APP_CONST_INJECTOR =  new  InjectionToken<AppConstants>('app.config');

You can have the class AppConstants, the value APP_CONSTANTS and the token APP_CONST_INJECTOR in a single file named appconstant.ts.

The following snippet shows the portion of the module file that registers the constant:

// other import statements
import  {  InjectionToken }  from  '@angular/core';
export const APP_CONST_INJECTOR =  new  InjectionToken<AppConstants>('app.config');

@NgModule({
  providers:  [
    /* Other providers */,
    { provide: APP_CONST_INJECTOR, useValue: APP_CONSTANTS }]
})
export class AppModule {}

To inject this constant, we need the token and the Inject decorator. To be able to use the constant in the official TypeScript way, we need the class AppConstant.

The constant can be used in PlaceComponent, as it has a textbox accepting a number and we can apply these values for min and max values of the input. The following snippet shows the class of the PlaceComponent and it uses the constant registered above:

import { Component, Input, Output, EventEmitter, Inject } from '@angular/core';
import { Place } from './place.model';
import { APP_CONST_INJECTOR, AppConstants } from './appconstant';

@Component({
  selector: 'place',
  templateUrl: './place.component.html'
})
export class PlaceComponent {
  
  constructor(@Inject(APP_CONST_INJECTOR) private appConstants: AppConstants) {
    
  }
  
  get maxRating(){
    return this.appConstants.maxRating;
  }
  
  get minRating(){
    return this.appConstants.minRating;
  }

  @Input('selectedPlace')
  place: Place;
  placeChanged: string;

  @Output('toggleVisited')
  toggleVisited = new EventEmitter<string>();

  togglePlaceVisited() {
    this.toggleVisited.emit(this.place.name);
  }

  get IsVisited(): string {
    return this.place.isVisited? 'Yes' : 'No';
  }
}

Conclusion

Dependency Injection is one of the most important features that Angular provides to build scalable web applications. As this tutorial demonstrated, the API for DI in Angular allows us to inject anything, thus making the code extensible and testable. In a forthcoming tutorial on Unit Testing in Angular, we will see you how these features help in unit testing.

This article was technically reviewed by Keerti Kotaru.

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook