Before starting to read about DI providers, lets know what is Dependency Injection mechanism in Angular used for.
Angular DI is a design pattern where a class requests for a service or dependencies from external resources rather than creating its own. If @Injectable()decorator is added above class with { providedIn: ‘root’ } object inside it, then it is added to the angular DI system which is visible throughout the application.When a class require a dependency it is injected inside its constructor as shown below.
Example:
constructor(myService: MyService) { }
// use access modifiers to enable encapsulation
The above method is used for making a module or service available at root level and through out the application, another way developers use for limiting the provider scope is by adding a providers array inside @Component decoration.
If a providers meta-data is declared inside a @ngModule decorator then this dependency is injected to all components of this @ngModule .
If a providers mete-data is declared inside a @Component decorator then it is injected to itself and all its children components only.
Example:
@Component({
/* ... */
providers: [MyService],
})
The above providers value is expanded to below form,
providers: [
{ provider: MyService, useClass: MyService }
]
Here comes the main part of the article. The above two key/value pairs inside the object specifies the provider token and provider definition respectively. The 2nd property here can be changed depending upon the requirement for type of dependency needed.
There are four types of provider definition available in angular,
- useClass
- useValue
- useExisting
- useFactory
Lets begin with ‘useClass’:
providers: [
{ provider: SomeService, useClass: BetterService }
]
This tell the injector to return a BetterService if a constructor requests for SomeService using the SomeService token.
Still not got it, lets take another example.
providers: [
{ provider: FormsModule, useClass: XFormsModule }
]
Since we have formsModule in angular/forms, we have a plenty of validators in NG_VALIDATORS but still we require other validators for more secure forms and user experience, so we can create our own XFormsModule that has custom validators which also extends FormsModule to inherit existing validators in case might require.
Now when ever FormsModule is injected in a constructor, angular DI will create an instance of XFormsModule.
useClass DI provider is helpful to create an instance of our own implementation service or class which can serve as an alternative to an existing one.
useValue :
providers: [
{ provider: SomeService, useValue: value }
]
In the above syntax, when ever a constructor requests for SomeService then the useValue key is used to associate the variable with the given token.
So, if SomeService returns a string saying ‘welcome’ then we can pass a string ‘Welcome back’ to useValue option for fulfilling the service role. For this you need to add @Inject() in constructor to provide reference to it.
constructor(@Inject(MESSAGE) private msg: string)
We can pass a number, string, array or object to useValue key. useValue itself is self explanatory, if you want to use a value use it this way.
Note: You can even create your own custom DI provider using InjectionToken.
useExisting :
providers: [
{ provide: AuthService, useExisting: Auth2Service }
]
This can be understood using an example where we are using AuthService where ever required, but when we implement a new AuthService named as Auth2Service and does’nt want to change AuthService code, we can use provider definition useExisting. This is used to provide an alias for the current provider. So, in above case AuthService is an alias for new Auth2Service. Whether you call any of the provider, only the new service provided in useExisting will be returned as a singleton instance.
The above example code will inject Auth2Service where ever AuthService is injected as shown in above example.
useFactory:
This provider is handy when we don’t have information to provide a service before runtime.
Lets take an example where we have a service called StorageService which allows to store x amount of data and is available for all visiting users, in addition to this authorised or signed-in users have access to additional x + y amount of storage. This service can not be set static because we can not get user information before run time, so we use provider useFactory to dynamically check for some boolean value to provide StorageService depending upon user authorisation.
Example:
export class StorageService {
constructor(private isAuthorised: boolean) {
this.isAuthorised ? this.saveExtraData() : this.saveData();
}
saveData() {
// restricting user to store extra data code ...
console.log(‘data saved’);
}
saveExtraData() {
// no restrictions to authenticated user ...
console.log(‘data saved’);
}
}
To make this work we need a FactoryProvider -
// Factory provider for creating a new instance for StorageService
export let StorageServiceFactory(userService: UserService) {
return new StorageService(userService.user.isAuthorized);
}
Note: Here I am using functions to return different functionality depending upon user, but you can create two independent services and return either service from the factoryProvider function.
// app.component.ts
@Component({
providers: [
{ provider: StorageService,
useFactory: StorageServiceFactory,
deps: [UserService] }
]
})
Here deps property defines an array of provider tokens. UserService serve as tokens for their own class providers. This will inject the service into the StorageServiceFactory function parameter.
Hope you understand about DI providers with this article, you are welcome to suggest edits and corrections in the comments section.
Top comments (0)