在我的主app.ts
中,我宣布了一个全球提供商:
providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
(其中createDependency
只是一个返回具有getName()
方法的类的函数。)
我也有一个组件:
<my-app-component-3>Hello from 3</my-app-component-3>
代码:
@Component({
selector: 'my-app-component-3',
template: `
<div>Component3:
<ng-content></ng-content>
: <span [innerHTML]="dependency?.getName()"></span>
</div>
`,
})
export class Component3 {
constructor(@Host() @Optional() public dependency: Dependency) {}
}
结果是:
组件3:你好3:
但我希望结果是:
组件3:3您好: AppModule提供程序
因为基本上app结构是:
<my-app>
<my-app-component-3>
</my-app-component-3>
</my-app>
问题:
为什么@Host()
与父提供商不匹配?
(即:providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
)
据我所知 - 注射器应该以这种方式寻找Dependency
:
那么为什么它找不到呢?
通知
我已经知道如果我删除@host
- 它确实会到达顶部。我的问题是为什么添加@host - 没有达到顶峰 - 尽管my-component3
位于my-app
!!
答案 0 :(得分:6)
查看A curios case of the @Host decorator and Element Injectors in Angular,深入了解@Host装饰器的工作原理以及Element Injectors在这张图片中的位置。
为了使其起作用,您应该在父组件的中定义依赖关系并使用 viewProviders :
@Component({
selector: 'my-app',
viewProviders: [{provide: Dependency, useValue: createDependency('AppModule provider')}],
...
export class MyApp {}
以下是metadata.ts内的评论:
指定注入器应从any检索依赖项 注入器直到到达当前组件的主机元素。
所以基本上它表示在解析依赖关系时使用主机元素注入器和以上的所有注入器。因此,如果您的MyApp
组件具有以下模板:
<my-app-component-3></my-app-component-3>
,生成的组件树如下所示:
<my-app>
<my-app-component-3></my-app-component-3>
</my-app>
MyApp
组件的注入器和App模块注入器 均未用于解析my-app-component-3
的依赖关系。
但是,ProviderElementContext._getDependency中有以下有趣的代码执行一项额外的检查:
// check @Host restriction
if (!result) {
if (!dep.isHost || this.viewContext.component.isHost ||
this.viewContext.component.type.reference === tokenReference(dep.token !) ||
// this line
this.viewContext.viewProviders.get(tokenReference(dep.token !)) != null) { <------
result = dep;
} else {
result = dep.isOptional ? result = {isValue: true, value: null} : null;
}
}
基本上检查提供程序是否在viewProviders
中定义,如果找到则解析它。这就是viewProviders
工作的原因。
所以,这是查找树:
此装饰器主要用于指令,以便从当前组件视图中的父注入器解析提供程序。甚至只有unit test is written来测试指令。以下是forms
模块中如何使用它的装饰器的真实示例。
考虑A
组件的此模板:
<form name="b">
<input NgModel>
</form>
NgModel
指令想要解析form
指令提供的提供程序。但如果提供商不可用,则无需超出当前组件A
。
所以NgModel
定义如下:
export class NgModel {
constructor(@Optional() @Host() parent: ControlContainer...)
虽然form
指令定义如下:
@Directive({
selector: '[formGroup]',
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
...
})
export class NgForm
此外,如果指令使用viewProviders
定义,则指令可以注入由其托管组件定义的依赖项。例如,如果MyApp
组件定义如下:
@Component({
selector: 'my-app',
viewProviders: [Dependency],
template: `<div provider-dir></div>`
})
export class AppComponent {}
Dependency
将被解决。
答案 1 :(得分:0)
我想知道@Optional()是否注入了null。我相信一个人可能是罪魁祸首。
所以从你的plunker我似乎找不到组件3的实际主机。像
<parent-component>
<component-3><component-3/>
<parent-component/>
根据我的理解here,它似乎正在寻找它。
答案 2 :(得分:0)
从组件3构造函数中删除@Host()装饰器:
Component({
selector: 'my-app-component-3',
template: `
<div>Component3:
<ng-content></ng-content>
: <span [innerHTML]="dependency?.getName()"></span></div>
`,
})
export class Component3 {
constructor(@Optional() public dependency: Dependency) {}
}
Angular将从AppModule中获取提供程序。
答案 3 :(得分:0)
直接来自Angular的关于依赖注入和主持人装饰的文档:https://angular.io/guide/dependency-injection-in-action#qualify-dependency-lookup-with-optional-and-host
The @Host decorator stops the upward search at the host component.
The host component is typically the component requesting the dependency.
使用@Host装饰器,您告诉它只检查提供商的主机组件,并且您将其设为可选项,因此它只是看到那里没有提供者和退出。
在实践中,Host装饰器的用例非常狭窄,如果您正在预测内容,那么这种用例真的很有意义。