Simplifying Different Types of Providers in Angular
In this tutorial, we go over the several different providers that are available to developers in the Angular framework, and how to use them in your TypeScript code.
Join the DZone community and get the full member experience.
Join For FreeAn 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 components, pipes, or directives. There is a lot of jargon involved here, so to understand the purpose of the 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 into the providers
array, and injecting it in the constructor of the component. We are calling the service method on the click of the button, and you can see the error message passed in the console. Very simple, right?
Here, Angular will rely on the values passed in the providers
array of the component (or module) to find which instance should be injected at runtime.
A provider determines how an object of a certain token can be created. So, when you pass a service name into the
providers
array of either component or module, like below:
providers: [ErrorService]
Angular is going to use the token value of ErrorService
and, for the token ErrorService
, it will create an object of the 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:
- Locating the dependency value.
- Registering the dependency.
The second property (of four types) is used to create the dependency value. There are four possible values of the second parameter, as follows:
- useClass
- useExisting
- useValue
- useFactory
We just saw an 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 the instance of NewErrorService
to be injected rather than the instance of ErrorService
. Also, ideally, both classes will be implementing the same interface, which means they will have the same method signatures with different implementations. So now, for the token ErrorService
, we want the instance of NewErrorService
to be injected. This 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 the token ErrorService
instance of NewErrorService
will be created.
Let us modify the component to use NewErrorService
.
mport { 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 purposes, I am injecting a service into the component, however, to use the service at the module level, you can inject in the module itself. Now, when the 'set error' button is clicked, NewErrorService
will be called.
useValue
Both useClass
and useExisting
create an instance of a service class to inject for a particular token, but sometimes you want to pass a value directly, instead of creating an instance. So, if you want to pass a readymade object instead of an 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 the token ErrorService
, Angular will inject the object.
useFactory
There could be a scenario where, until runtime, you don't have any idea about what instance is needed. You need to create a dependency based on information you don't have until the last moment. Let us consider, for example, that you may need to create an instance of ServiceA if the user is logged in or ServiceB if the user is not logged in. Information about the logged in user may not available until the last minute or may change during the use of the application.
We need to use useFactory
when information about the dependency is dynamic. Let us consider our two services used in the previous examples:
- ErrorMessageService
- NewErrorMessageService
For the 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 an instance for a particular token. Also, keep in mind that in useFactory
, you can pass a dependency. So assume that you have a dependency on LoginService
to create an instance for the 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.
Published at DZone with permission of Dhananjay Kumar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments