Simplifying Different Types of Providers in Angular

Dhananjay Kumar / Wednesday, July 11, 2018

An Angular Service provider delivers a runtime version of a dependency value. Therefore, when you inject a service, the Angular injector looks at the providers to create the instance of the service. It is the provider that determines which instance or value should be injected at the runtime in component, pipes, or directives. There are many jargons involved here, so to understand purpose of types of providers, let us start with creating a service.  Let’s say we have a service called ErrorService, which is just logging the error message.

import { Injectable } from '@angular/core';
 
@Injectable()
export class ErrorService {
 
    logError(message: string) {
        console.log(message);
    }
}

Now, we can use this service in a component, as shown in the listing below:

import { Component } from '@angular/core';
import { ErrorService } from './errormessage.service';
 
@Component({
    selector: 'app-root',
    template: `
  <input [(ngModel)]='err' type='text'/>
  <button (click)='setError()'>set error</button>
  `,
    providers: [ErrorService]
})
export class AppComponent {
    constructor(private errorservice: ErrorService) { }
    err: string;
    setError() {
        this.errorservice.logError(this.err);
    }
 
}

We are importing the service, passing it in the providers array, and injecting it in the constructor of component. We are calling the service method on click of the button, and you can see error message passed in the console. Very simple right?

Here, Angular will rely on the values passed in the providers array of component (or module) to find which instance should be injected at the run time.

 “A Provider determines that how object of certain token can be created.”

So, when you pass a service name in providers array of either component or module, like below:

providers[ErrorService]

Here, Angular is going to use token value ErrorService and, for token ErrorService, it will create object of ErrorService class. The above syntax is a shortcut of the below syntax:

providers[{
    provide: ErrorService, useClass: ErrorService
}]

The provide property holds the token that serves as the key for

  1. locating the dependency value.
  2. registering the dependency.

The second property (it is of four types) is used to create the dependency value. There are four possible values of second parameter, as follows:

  1. useClass
  2. useExisting
  3. useValue
  4. useFactory

We just saw example of useClass. Now, consider a scenario that you have a new class for better error logging called NewErrorService.

import { Injectable } from '@angular/core';
 
@Injectable()
export class NewErrorService {
 
    logError(message: string) {
        console.log(message);
        console.log('logged by DJ');
    }
 
}

useExisting

Now, we want that instead of the instance of ErrorService, the instance of NewErrorService should be injected.  Also, ideally, both classes must be implementing the same Interface, which means they will have same method signatures with different implementation.  So now, for the token ErrorService, we want the instance of NewErrorService to be injected. It can be done by using useClass, as shown below:

providers[
    NewErrorService,
    { provide: ErrorService, useClass: NewErrorService }
]

The problem with the above approach is that there will be two instances of NewErrorService. This can be resolved by the use of useExisting.

providers[
    NewErrorService,
    { provide: ErrorService, useExisting: NewErrorService }
]

Now there will be only one instance of NewErrorService and for token ErrorService instance of NewErrorService will be created.  

Let us modify component to use NewErrorService. 

import { Component } from '@angular/core';
import { ErrorService } from './errormessage.service';
import { NewErrorService } from './newerrormessage.service';
 
@Component({
    selector: 'app-root',
    template: `
  <input [(ngModel)]='err' type='text'/>
  <button (click)='setError()'>set error</button>
  <button (click)='setnewError()'>Set New eroor</button>
  `,
    providers: [
        NewErrorService,
        { provide: ErrorService, useExisting: NewErrorService }
    ]
})
export class AppComponent {
    constructor(private errorservice: ErrorService, private newerrorservice: NewErrorService) { }
    err: string;
    setError() {
        this.errorservice.logError(this.err);
    }
    setnewError() {
        this.newerrorservice.logError(this.err);
    }
 
}

Here, for demonstration purpose, I am injecting service in the component, however, to use service at the module level you can inject in the module itself. Now, on the click of set error button NewErrorService would be called.

useValue

Both useClass and useExisting create instance of a service class to inject for a particular token, but sometimes you want to pass value directly instead of creating instance. So, if you want to pass readymade object instead of instance of a class, you can use useValue as shown in the below listing:

providers[
    {
        provide: ErrorService, useValue: {
            logError: function (err) {
                console.log('inhjected directly ' + err);
            }
        }
    }

So here, we are injecting a readymade object using useValue. So for token ErrorService, Angular will inject the object.

useFactory

There could be scenario where, until runtime, you don’t have idea about what instance is needed. You need to create dependency on basis of information you don’t have until the last moment. Let us consider example that you may need to create instance of ServiceA if user is logged in or ServiceB if user is not logged in. Information about logged in user may not available until last time or may change during the use of application.

We need to use useFactory when information about dependency is dynamic. Let us consider our two services used in previous examples:

  1. ErrorMessageService
  2. NewErrorMessageService

 For token ErrorService on a particular condition we want either instance of ErrorMessageService or newErrorMessageService. We can achieve that using useFactory as shown in the listing below:

providers[
    {
        provide: ErrorService, useFactory: () => {
            let m = 'old'; // this value can change
            if (m === 'old') {
                return new ErrorService();
            } else {
                return new NewErrorService();
            }
        }
    }
]

 

Here, we have taken a very hardcoded condition. You may have complex conditions to dynamically create instance for a particular token.  Also, keep in mind that in useFactory, you can pass dependency. So assume that you have dependency on LoginService to create instance for ErrorService token. For that pass LoginService is deps array as shown in the listing below:

providers[
    {
        provide: ErrorService, useFactory: () => {
            let m = 'old'; // this value can change
            if (m === 'old') {
                return new ErrorService();
            } else {
                return new NewErrorService();
            }
        },
        deps: [LoginService]
    }
]

These are fours ways of working with providers in Angular.  I hope you find this post useful. Thanks for reading.  If you like this post, please share it. Also, if you have not checked out Infragistics Ignite UI for Angular Components, be sure to do so! They have 30+ material based Angular components to help you code web apps faster.