DI Resolution modifiers is the decorator we use for Dependency Injection.
constructor(@Inject(Document) private document: HTMLDocument) {}orconstructor(@Optional() private optionalService: OptionalService) {}
You might have used it before, or just seen on some library and never used it before. Either ways, you can learn what that is and how angular manages dependencies by one by one in this article.
| What @Inject means?
constructor(private messageService: MessageService) {}
Most time we declare dependencies of the class like that and angular detect which service or instance it is depends on and takes care of creating and injecting the one. So when angular sees that clause, it knows the class should receive the MessageService and tries to find the one.
But sometimes this declaration does not work, like the case below, you want to inject specific value with some meaning, it is hard for angular to figure out what dependency it should inject.
constructor(private isDev: boolean) {
// what should it refer to???
}
@Inject decorator is the manual indicator tells angular what it should look for. Dependency is composed with provide and valueFactory(useValue, useExisting, useFactory etc..) parts and InjectionToken class is used to as the provide to make it distinguishable.
| How Injector manages dependencies.
Dependency Injection uses Injector mechanism so it is important for us to know the concept of Injector.
Angular provides 2 types of injector, one is module Injector and the other is Element Injector. (link)
@Injectable()
export class AppService {}@Component({
...,
providers: [AppService]
})
export class AppComponent {}@NgModule({providers: [AppService]})
export class AppModule {}
Even though AppService is one, depends on whether it is provided in module level or component level, they receive different type of parent injector.
Element Injector and Module Injector form separate trees of injector. Injector instance has the prop referencing parent and angular traverse Injector tree up until it finds the dependency and gives you the closest one. Otherwise it reaches the first Injector which is nullInjector and return the error.
(It traverse element Tree first, then does module one.).
Now enough of DI, we can back to the modifier and check one by one.
Modifiers
- @Optional
When you create libraries or some class for lazy modules, you can’t assume that every service would exist at the time of boosting. Angular throws an error when it fails to retrieve the dependency, and @Optional modifier prevents the error from happening. (It just prevent injection error, and the responsibility of using it is on you.)
constructor(@Optional() private appService: AppService) {
}// you should check whether the dependency is injected or not manually.
getAppServiceValue(){
return this.appService ? this.appService.value : 0;
}
- @Self
Besides Optional, @Self is the next one to use often. In case you don’t want your service to receive dependency from above but from same level.
Let’s say you made a LayerService that shows some contents and this layer might close differently by it has button or Not (controlled by LayerButtonService).
Layer might have layer inside so we can’t receive any LayerButtonService.
Yes, we can make layerService to receive optionally layerButtonService only it is injected in the same level.
@Injectable()
export class LayerService {
constructor(@Optional() @Self buttonService: LayerButtonService){}
}@Component({
selector: 'layer',
providers: [LayerService],
...
})
export class LayerComponent { .... }@Component({
selector: 'dropdown',
providers: [LayerButtonService, LayerService],
...
})
export class DropdownComponent { .... }
Also at Directive, you can check the directive is attached to specific class and maybe runs different logic.
- @SkipSelf
SkipSelf is opposite of the Self. it will check dependencies declared on the parents injector.
You can check depth or other values to check accumulation.
@Injectable()
export class LayerService {
public depth: number;
constructor(@Optional() @SkipSelf() parentLayer: LayerService) {
this.depth = parentLayer ? parentLayer.depth + 1 : 0;
}
}@Component({
selector: 'layer',
template: '<div>layer depth {{depth}}<layer [nth]="nth - 1" *ngIf="nth > 0"></layer></div>',
providers: [LayerService],
})
export class LayerComponent {
@Input() nth = 0;
depth;
constructor(public layerService: LayerService) {
this.depth = layerService.depth;
}
}
- @Host
Host retrieve dependencies from host element or component. So in some cases it is similar to @Self in the component level, but with other concepts it shows different outcome. I really does not have a chance to use it, but in case you want to get the component injector from directive, you can use it with only viewProviders.
Sadly, I haven’t figure out why viewProvider and provider gives different result.
Anyway I hope this article would help you understand more of angular DI.