Angular中viewProviders与@Host协同机制及注入疑问
@Host()+@SkipSelf()与viewProviders的DI疑惑 示例代码
LogService
@Injectable() export class LogService { sayHello() { console.log('Hello World !'); } }
ChildComponent
@Component({ selector: 'app-child', standalone: true, template: ` <div>Child Component !</div> `, }) export class ChildComponent { constructor(@Host() @SkipSelf() logService: LogService) { logService.sayHello(); // 输出 'Hello World !' } }
ParentComponent
@Component({ selector: 'app-parent', imports: [ChildComponent], standalone: true, viewProviders: [LogService], template: ` <h1>Parent Component</h1> <app-child></app-child> `, }) export class ParentComponent {}
疑惑点
我无法理解为何ChildComponent构造函数中同时使用@Host()和@SkipSelf()装饰器时,LogService仍能被正确注入。
根据@Host文档说明:
@Host属性装饰器会将依赖查找的向上搜索终止于宿主组件。宿主组件通常是请求依赖的组件,但当该组件被投影到父组件中时,父组件会成为宿主。
根据viewProviders文档说明:
定义对其视图DOM子组件可见的可注入对象集合
本示例中没有内容投影,按道理宿主组件应该是ChildComponent。依赖查找应该在ChildComponent处终止,再加上@SkipSelf()会跳过自身的注入器,那DI框架为什么能找到ParentComponent的viewProviders里声明的LogService?
我原本以为viewProviders的作用等同于在ChildComponent的providers中提供服务,但@SkipSelf()应该会阻止在该组件内部查找。把viewProviders换成providers时,会出现预期的错误No provider for LogService found。由于viewProviders的文档说明太简略,我搞不懂它的底层工作机制。
问题解析
要搞懂这个问题,得明确三个核心关键点:
1. 「宿主组件」的实际指向
当组件不是通过<ng-content>内容投影嵌入父组件时,宿主组件指的是包含当前组件视图的父组件(也就是示例里的ParentComponent),而非请求依赖的ChildComponent本身。@Host()装饰器的作用是把依赖查找范围限定在宿主组件的注入器层级内,不会再向上查找更高层级的注入器。
2. viewProviders与providers的作用域差异
providers声明的服务会进入组件的元素注入器,作用域覆盖组件自身、以及通过<ng-content>投影进来的子组件。viewProviders声明的服务会进入组件的视图注入器,作用域覆盖组件自身、以及组件模板里直接引用的子组件(比如ParentComponent模板里的<app-child>),但不包含内容投影的组件。
3. DI的具体查找流程
在ChildComponent的构造函数中,@SkipSelf()会跳过自身的所有注入器,然后@Host()限定查找宿主组件ParentComponent的注入器层级:
- 首先查找
ParentComponent的视图注入器,这里正好通过viewProviders声明了LogService,因此直接匹配成功并注入。 - 当把
ParentComponent的viewProviders换成providers时,LogService会进入ParentComponent的元素注入器,而ChildComponent不是通过内容投影嵌入的,DI不会去查找宿主组件的元素注入器,因此会出现No provider for LogService found的错误。
内容的提问来源于stack exchange,提问作者Olivier Boissé

