Angular 中的注入器有一些规则,你可以利用这些规则来在应用程序中获得所需的可注入对象可见性。通俗的讲,这些规则指定了注入器的“作用域"。
两个注入器层次结构
Angular 中有两个注入器层次结构:ModuleInjector层次结构 —— 在@NgModule()或@Injectable()中提供的服务。ElementInjector层次结构 —— 在@Directive()或@Component()中提供的服务。
服务查找规则(令牌解析规则)
-
优先查找 ElementInjector,如果当前组件找不到提供者,将会去父组件中的ElementInjector -
当所有的 ElementInjector都找不到,就会去ModuleInjector中找 -
如果 ModuleInjector也找不到,就会抛出一个错误 -
对于同名的令牌,只会解析遇到的第一个依赖
解析修饰符
默认情况下,Angular 始终从当前的 Injector 开始,并一直向上搜索。但修饰符使你可以更改开始(默认是自己)或结束位置。
- 如果
Angular找不到你要的服务怎么办,用@Optional()阻止报错 -
用 @SkipSelf()跳过自身,从父组件(指令)开始找 -
用 @Self()只在当前组件(指令)找 -
用 @Host()只在当前组件(指令)宿主上找
logger组件和一个logger服务来验证上面的规则:// logger.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class LoggerService {
constructor() { }
log(message: string) {
console.log(message);
}
}
// logger.component.ts
...
@Component({
selector: 'app-logger',
template: `<p>logger works!</p>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoggerComponent implements OnInit {
constructor(private loggerservice: LoggerService) { }
ngOnInit(): void {
this.loggerservice.log('这是组件内打印的日志');
}
}
logger服务,浏览器会报没有提供服务的错:
当我们给服务加上@Optional()装饰器:
// logger.component.ts
constructor(@Optional() private loggerservice: LoggerService) { }

providers没有提供服务,而在根模块中提供logger服务:// app.module.ts
@NgModule({
//...
providers: [LoggerService],
})
@SkipSelf()跳过自身:// logger.component.ts
constructor(@Optional() @SkipSelf() private loggerservice: LoggerService) { }
@Self()同理,当明确知道只需要在自身组件内找服务时使用。对于@Self()与@Host()的区别,官网有这么一句话:
@Host()会禁止在宿主组件以上的搜索。宿主组件通常就是请求该依赖的那个组件。不过,当该组件投影进某个父组件时,那个父组件就会变成宿主。那我们就使用投影来解释下这个区别。
ng g c components/logger/logger-content -s -t
// logger.component.ts
@Component({
selector: 'app-logger',
template: `
<div>
<p>logger works!</p>
<ng-content></ng-content>
</div>
`,
providers: [LoggerService]
})
调用logger-content组件:
<app-logger> <app-logger-content></app-logger-content> </app-logger>
上面以内容投影的方式将logger-content组件引入到logger组件,并只在logger组件提供服务。
// logger-content.component.ts
export class LoggerContentComponent implements OnInit {
// 使用@Host()装饰器
constructor(@Host() private loggerservice: LoggerService) { }
ngOnInit(): void {
this.loggerservice.log('logger-content 组件打印的内容!');
}
}
@Host(),我们是可以获取到logger-service的,即用@Host()依然能访问父组件的ElementInjector。logger-content组件不是通过投影的方式引入,app-logger-content就是它的宿主。但是通过投影方式引入,引入它的组件就是它的宿主。(不太确定这样的说法是否正确,但通过@Host()装饰器的解释,或许可以这样理解)其实一般情况下,@Host()可以完全替换@Self()的,他们之间的区别真的很微妙。
指定NgModule的注入
providedIn: 'root'指定从根模块中提供该服务,其实也可以像下面这样指定其它模块提供该服务。(这里有个前提:我们是将logger组件分配到ComponentsModule模块的)// logger.service.ts
@Injectable({
providedIn: ComponentsModule
})
浏览器也会报错:

所以,只能在其它引入了ComponentsModule的NgModule中,才能使用该服务。
viewProviders
viewProviders 数组是在 @Component() 装饰器中提供服务的另一种方法。ElementInjector,直到找到为。viewProviders也有这个特性,区别是,对投影内容不可见。viewProviders的修饰符与providers的修饰符用法一致。<app-logger> <app-logger-content></app-logger-content> </app-logger>
LoggerContentComponent无法找到LoggerComponent里用viewProviders提供的服务。总结
- Angular提供两个注入器层次结构:
ModuleInjector层次结构及ElementInjector层次结构; - 服务查找规则可以通俗的理解为‘就近原则’;
- 通过配置不同的解析修饰符,可以对程序进行一定程度的优化;
viewProviders对投影内容不可见。
本文转载自:岩弈,版权归原作者所有,本博客仅以学习目的的传播渠道,不作版权和内容观点阐述,转载时根据场景需要有所改动。


最新评论