Angular 的`@Host` 装饰器没有到达顶部? [英] Angular's `@Host` decorator not reaching the top?
问题描述
在我的主要 app.ts
中,我声明了一个全局提供程序:
providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
(其中 createDependency
只是一个返回具有 getName()
方法的类的函数.)
我也有一个组件:
<my-app-component-3>Hello from 3</my-app-component-3>
代码:
@Component({选择器:'my-app-component-3',模板:`<div>组件3:<ng-content></ng-content>:<span [innerHTML]="dependency?.getName()"></span>
`,})导出类 Component3 {构造函数(@Host() @Optional() 公共依赖:依赖) {}}
结果是:
<块引用>Component3:来自 3 的你好:
但我希望结果是:
<块引用>Component3: Hello from 3 :AppModule provider
因为基本上应用结构是:
<my-app-component-3></my-app-component-3></我的应用程序>
问题:
为什么 @Host()
不匹配父提供者?
(即:providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
)
据我所知 - 注入器应该以这种方式寻找 Dependency
:
那它为什么没有找到呢?
用法
这个装饰器主要用于指令从当前组件视图中的父注入器解析提供者.甚至单元测试都写好了 仅用于测试指令.这是 forms
模块中的一个真实示例,说明如何使用它的装饰器.
考虑这个 A
组件的模板:
NgModel
指令想要解析由 form
指令提供的提供者.但是,如果提供程序不可用,则无需超出当前组件 A
.
所以 NgModel
是这样定义的:
导出类 NgModel {构造函数(@Optional() @Host() 父级:ControlContainer...)
虽然 form
指令是这样定义的:
@Directive({选择器:'[formGroup]',提供者:[{提供:ControlContainer,useExisting:FormGroupDirective}],...})导出类 NgForm
此外,如果使用 viewProviders
定义,指令可以注入由其托管组件定义的依赖项.例如,如果 MyApp
组件定义如下:
@Component({选择器:'我的应用',视图提供者:[依赖],模板:`<div provider-dir></div>`})导出类 AppComponent {}
依赖
将得到解决.
In my main app.ts
I've declared a global provider :
providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
(Where createDependency
is just a function that returns a class which has a getName()
method.)
I also have a components :
<my-app-component-3>Hello from 3</my-app-component-3>
Code :
@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) {}
}
The result is:
Component3: Hello from 3 :
But I expect the result to be :
Component3: Hello from 3 :AppModule provider
Because basically the app structure is :
<my-app>
<my-app-component-3>
</my-app-component-3>
</my-app>
Question:
Why doesn't @Host()
match the parent provider ?
(which is : providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]
)
To my knowledge - the injector should seek for a Dependency
in this manner :
So why doesn't it find it ?
Notice
I already know that if I remove @host
- it does reach the top. My question is why adding @host - is not reaching the top - despite the fact thatmy-component3
is under my-app
!!
Check out A curios case of the @Host decorator and Element Injectors in Angular for in-depth explanation of how @Host decorator works and where Element Injectors come into this picture.
In order for it to work you should define dependencies in the in the parent component and using viewProviders:
@Component({
selector: 'my-app',
viewProviders: [{provide: Dependency, useValue: createDependency('AppModule provider')}],
...
export class MyApp {}
Here is what the comments inside metadata.ts say:
Specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component.
So basically it says that a host element injector and all injectors above are not used when resolving a dependency. So if your MyApp
component has the following template:
<my-app-component-3></my-app-component-3>
and the resulting components tree look like this:
<my-app>
<my-app-component-3></my-app-component-3>
</my-app>
neither MyApp
component's injector nor App module injectors are used to resolve dependency for the my-app-component-3
.
However, there's the following interesting code in the ProviderElementContext._getDependency that performs one additional check:
// 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;
}
}
which basically checks if the provider is defined in the viewProviders
and resolves it if found. That's why viewProviders
work.
So, here is the lookup tree:
Usage
This decorator is mostly used for directives to resolve providers from the parent injector within the current component view. Even the unit test is written only to test directives. Here is a real example from the forms
module how it's decorator is used.
Consider this template for the A
component:
<form name="b">
<input NgModel>
</form>
NgModel
directive wants to resolve a provider supplied by the form
directive. But if the provider is not available, there's no need to go outside of a current component A
.
So NgModel
is defined like this:
export class NgModel {
constructor(@Optional() @Host() parent: ControlContainer...)
While form
directive is defined like this:
@Directive({
selector: '[formGroup]',
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
...
})
export class NgForm
Also, a directive can inject dependencies defined by its hosting component if they are defined with viewProviders
. For example, if MyApp
component is defined like this:
@Component({
selector: 'my-app',
viewProviders: [Dependency],
template: `<div provider-dir></div>`
})
export class AppComponent {}
the Dependency
will be resolved.
这篇关于Angular 的`@Host` 装饰器没有到达顶部?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!