Angular 12 Providers | useValue, useClass & useFactory Tutorial with Examples

In this tutorial, we will learn about Providers. How to implement different types of providers like useValue, useClass and useFactory in Angular 12,11,10,9,8,7,6,5,4 application projects with practical and simple examples. Let’s start with Provider and what role exactly it plays in Dependency Injection. We can say Provider is the ‘How’ part of the DI Token…

By.

•

min read

In this tutorial, we will learn about Providers. How to implement different types of providers like useValue, useClass and useFactory in Angular 12,11,10,9,8,7,6,5,4 application projects with practical and simple examples.

Let’s start with Provider and what role exactly it plays in Dependency Injection.

We can say Provider is the ‘How’ part of the DI Token of Dependency. Its helps in How to create/find the Dependency for a token.

You can check the previous post on Providers here.

Providers are used to registering classes, functions, or values as dependencies using Angular’s Dependency Injection (DI) system. Each Provider is located using a token. Three types of token available are available:

  • Type Token
  • String Token
  • Injection Token

Four ways can be used to create the dependency:

  • useClass : Class Provider
  • useValue: Value Provider
  • useFactory: Factory Provider
  • useExisting: Class as Alias Provider

 

[lwptoc]

Ways to Register Services

There are two ways to provide the services in Angular application:

  • Adding in the providers array of Decorators like @NgModule(), @Component() or @Directive() metadata.
  • Using the providedIn property of @Injectable() decorator in Service itself.

 

When we register a service in the providers array as shown below:

import { Component } from '@angular/core';
import { LoggerService } from './services/logger.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers:[LoggerService]
})
export class AppComponent {

}

It actually converted into this under the hood:

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

Provide: Holds the Token/ DI Token. This is used by Injector to locate the provider in its Providers look-up array. This token can be a Type, String or Injection Token.

Provider: Second property is the instruction that tells Angular, how to create the dependency instance. As we already discussed it has 4 types useClass (default), useValue, useFactory or useExisting.

 

Ways to Provide DI Token

Three ways of defining the provider token are Type, String, and Injection Token.

Type

The type which we’re injecting is used as a Token here. For example, registering the LoggerService can be written as below:

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

Here the Token and Provider are the same, so we can write them simply as

providers:[ LoggerService ]

In this case, the service class is acting as a provider token. It can be simply used inside the class constructor

constructor(
    private loggerService: LoggerService
  ){}

 

String Token

The token name can be provided as a String. This becomes useful in the case of useValue (We will read more) where we just need to provide Provider as a string as shown below:

@NgModule({
  ....
  providers: [
    {provide:'IFRAME_URL', useValue: './assets/car/dash' }, 
    {provide:'API_BASE_PATH', useValue: './api/rest/open/' },
    {provide:'IS_PUBLIC', useValue: true }
  ],
  bootstrap: [AppComponent]
})

To use them, we can inject as a dependency using the @inject method

export class AppComponent {

  constructor(
    @Inject('IFRAME_URL') private iframeUrl:string,
    @Inject('APIURL') private apiBasePath:string,
    @Inject('IS_PUBLIC') private isPublic:boolean,
     ){}
  ...
}

 

Injection Token

Using String token can be risky if any other team members create duplicate Token names. This may cause an overridden behavior resulting in breaking the application.

To resolve this we can use the InjectionToken class to create unique Tokens every time.

You can keep them in a separate providers.ts file

import { InjectionToken } from "@angular/core";

export const IFRAME_URL= new InjectionToken<string>(''); 
export const API_BASE_PATH= new InjectionToken<string>(''); 
export const IS_PUBLIC= new InjectionToken<boolean>('');

 

In AppModule update providers array

import { API_BASE_PATH, IFRAME_URL, IS_PUBLIC } from './providers/providers';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [
    {provide:IFRAME_URL, useValue: './assets/car/dash' }, 
    {provide:API_BASE_PATH, useValue: './api/rest/open/' },
    {provide:IS_PUBLIC, useValue: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Use these Providers in the component as shown below:

export class AppComponent {
  constructor(
    @Inject(IFRAME_URL) private iframeUrl:string,
    @Inject(API_BASE_PATH) private apiBasePath:string,
    @Inject(IS_PUBLIC) private isPublic:boolean,
     ){}
}

Here we discussed the provider Token types. Next, we will discuss the type of Provider objects deciding the Dependency Injection.

 

Types of Providers

Angular Dependency Injection provides four types of providers:

  • useClass : Class Provider
  • useValue: Value Provider
  • useFactory: Factory Provider
  • useExisting: Class as Alias Provider

 

useClass : Class Provider

We use the useClass provider when we need to provide the instance of the provided class. it takes the type of class as Token and creates a new instance.

Example of useClass Provider

The LoggerService can be added in the providers array

providers:[LoggerService]

This can also be written as

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

The mapping of Token LoggerService is with LoggerService class.

If we want to switch the mapping above with any other class, it’s very easy to do it at once place as shown below:

providers:[{provide:LoggerService, useClass:HttpLoggerService}]

Now where ever we have injected/used our service, it will start pointing to HttpLoggerService instead of LoggerService.

To Dynamically switch classes in useClass based on environment or any other logic, we use useFactory which we will discuss later.

 

useValue : Value Provider

The useValue provider type is used when we need to give a simple value like String, Number, Boolean, or Object (using Object.freeze()), etc.

Examples of useValue Provider

We have already discussed how to use useValue in String Value and Injection Token sections. Moreover, we can also assign JSON Object which can be used in the application as a provider.

const USER_CONFIG =  Object.freeze({
  isAdmin: true,
  profileImage: './assets/images/profile/',
  age:21
});

@NgModule({
  ...
  providers: [
    {provide:'USER_CONFIG', useValue: USER_CONFIG }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Note: To make JSON Object immutable we need to wrap it with Object.freeze() to make it read-only. Otherwise, the config Object which can be changed loses its motive.

Now it can be used as shown below:

export interface USER {
  isAdmin: boolean;
  profileImage: string;
  age: number;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  isAdmin:boolean;

  constructor(
    @Inject('USER_CONFIG') private userConfig:USER,
     ){
       // assign value
       this.isAdmin = this.userConfig.isAdmin;
     }
}

 

useFactory: Factory Provider

The useFactory provides a function, so it becomes useful when we need to inject a returned value based on some conditional expressions. The optional deps array can be used to add required dependencies.

Example of useFactory Provider

Suppose we have a LoggerService and HttpLoggerService and we want to inject either of these two based on the PROD flag which we can easily get using the environment object.

Create a new MockLoggerService by executing the below generate command.

$ ng g service services/mock-logger

This MockLoggerService will return a function based on the environment. If the environment is developed then LoggerService will be returned otherwise HttpLoggerService is returned.

 

Step 1) Create Two Services

$ ng g service logger
LoggerService will have log() to print value using console.log(). This service will be returned by our Factory function when application is running development server like localhost.
// logger.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoggerService {

  constructor() { }

  log(msg:string){
    console.log(msg);
  }
}
$ ng g service http-logger
HttpLoggerService will have log() to post messages on remote logger database using post HTTP call. This service will be returned by our Factory function when application is running on production server.
// http-logger.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class HttpLoggerService {

  constructor(
    public httpClient: HttpClient
  ) { }

  log(msg:string){
    let data = {msg}
    console.log('HTTP LOGGER',{data});
    return this.httpClient.post('./api/log',data);
  }
}
Finally, create a Factory service that will return above created services based on the environment the application running.
// logger-factory.service.ts
import { HttpClient } from '@angular/common/http';
import { HttpLoggerService } from './http-logger.service';
import { LoggerService } from './logger.service';

export function LoggerFactoryService(
  httpClient: HttpClient,
  environment: any
) {

  if(
    environment['production']
  ){
    return new HttpLoggerService(
      httpClient
    );
  } else {
    return new LoggerService();
  }

}
Step 2) Update the provider in the app.module.ts file as shown below:
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [
    { provide: 'ENVIRONMENT', useValue: environment },
    {
      provide:LoggerService,
      useFactory: LoggerFactoryService,
      deps: [
        HttpClient,
        'ENVIRONMENT'
      ]
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Here the deps the property takes an array of dependencies required by each service. We also defined the 'ENVIRONMENT' provider of type useValue.

 

Step 3) Use the LoggerService anywhere in the application. It will auto pick the between the Logger and HTTP Logger service using the Factory function we defined as useFatory in the above step.

In AppComponent we simply import our service in the constructor to use its log() method.

export class AppComponent {
  constructor(
    private loggerService: LoggerService
     ){
       this.loggerService.log('This is a LOG!!!!');
     }
}

 

useExisting: Class as Alias Provider

The useExisting provider is used if we want to register a new Provider in place of the old Provider.

Example of useExisting Provider
providers: [
    { provide: LoggerService, useExisting: NewLoggerService },
    { provide: NewLoggerService, useClass: NewLoggerService },
...

Now, wherever we use LoggerService. it will return the NewLoggerService instance.

 

Conclusion

We discussed different types of Providers available in Angular with its different use-cases with examples.

Leave a Reply

Your email address will not be published. Required fields are marked *