8. Understanding Angular's Dependency Injection
In this section, we focus on Angular's dependency injection system.
We'll start with a look at dependency injection and how we tell Angular that we want a specific service.
1. Dependency Injection Overview
Before going in to more ways of defining/registering/consuming a service, we must have an overall understanding of how Angular works things out.
1.1. Defining a service
When we define and export a service, this is syntactically the same as a constructor function
@Injectable()
export class CustomService {
...
}
1.2. Registering a service
We need to register an instance of our defined service. Behind the scene, Angular takes the service class and creates an instance of it for our use.
@NgModule({
providers: [ CustomService ]
...
)}
export class AppModule { }
1.3. Consuming a service
To lookup a service, Angular uses the class defined CustomService.
The declaration type of our class serves as a key and Angular will look inside its Dependecy Injection registry to find the correct instance to inject.
import { Component } from '@angular/core'
import { CustomService } from './custom.service';
@Component({
templateUrl: './profile.component.html'
})
export class ProfileComponent {
constructor(private customService: CustomService) { }
}
2. Defining and registering a service with Angular's Injection token
If we take a look at the Toastr Service we've created previously, it has some inconveniences.
First, we are defining an entire new class to wrap an existing service. This is fine in this example but for a more complex 3rd party library, we don't want to rewrite all wrappers methods of our service.
⚠️ the toastr object doesn't really need a wrapper service, so is using globals, we must find a new way to inject our service properly.
💡 If we want to deal with things that aren't simple services we must procede differently. To register other kinds of services with the dependency injection system
we use a combination of the InjectionToken and the @Inject decorator.
We start by the definition of our service
import { InjectionToken } from '@angular/core';
export let TOASTR_TOKEN = new InjectionToken<Toastr>('toastr'); // description string used as 1st parameter
export interface Toastr {
success(message: string, title?: string): void;
info(message: string, title?: string): void;
warning(message: string, title?: string): void;
error(message: string, title?: string): void;
}
Next we need to register the service using the token we created previously.
import { TOASTR_TOKEN, Toastr } from './common/toastr.service';
declare let toastr: Toastr;
@NgModule({
providers: [
{
provide: TOASTR_TOKEN,
useValue: toastr
}
]
...
)}
export class AppModule { }
3. Consuming a service with the @Inject decorator
To consume our service we use the @Inject decorator and pass in our newly created token in the constructor of our consuming class.
import { Component, Inject } from '@angular/core'
import { TOASTR_TOKEN, Toastr } from './common/toastr.service';
@Component({
templateUrl: './profile.component.html'
})
export class ProfileComponent {
constructor(@Inject(TOASTR_TOKEN) private toastr: Toastr) { }
saveProfile() {
this.toastr.success('Profile saved!');
}
}
4. The useClass Provider
There are alternate methods we can use besides useValue to inject a service.
By default, when registering a service, we are in fact using useClass under the hood.
@NgModule({
providers: [ CustomService ]
...
)}
export class AppModule { }
The following code is equivalent to the previous example:
@NgModule({
providers: [
{
provide: CustomService, useClass: CustomService
}
]
...
)}
export class AppModule { }
💡 The useClass provider comes in handy when we want to inject a specific implementation of a class, for example:
@NgModule({
providers: [
{
provide: Logger, useClass: FileLogger
}
]
...
)}
export class AppModule { }
5. The useExisting Provider
This useExisting provider is also know as the "alias provider"
@NgModule({
providers: [
{
provide: MinimalLogger, useExisting: Logger
}
]
...
)}
export class AppModule { }
💡 This can be useful when we want to minize an API. For example, if we have a huge Logger service with 30 methods in it but we are only using 5 of them.
6. The useFactory Provider
This useFactory provider is used to provide a factory function
@NgModule({
providers: [
{
provide: Logger, useFactory: factory()
}
]
...
)}
export class AppModule { }
💡 This can be useful if we want to parameterize the creation of a specific service etc.