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 ProvideruseValue
: Value ProvideruseFactory
: Factory ProvideruseExisting
: 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 ProvideruseValue
: Value ProvideruseFactory
: Factory ProvideruseExisting
: 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);
}
}
// 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();
}
}
@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