Angular 的 `@Host` 装饰器没有到达顶部?

Angular's `@Host` decorator not reaching the top?

在我的主要 app.ts 中,我声明了一个全球供应商:

providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]

(其中 createDependency 只是一个 returns 一个具有 getName() 方法的 class 的函数。)

我还有一个组件:

    <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) {}
}

结果是:

Component3: Hello from 3 :

但我希望结果是:

Component3: Hello from 3 :AppModule provider

因为基本上应用程序结构是:

<my-app>
  <my-app-component-3>
  </my-app-component-3>
</my-app> 

问题:
为什么 @Host() 与父提供者不匹配?

(即:providers: [{provide: Dependency, useValue: createDependency('AppModule provider')}]

据我所知 - 注入器应该以这种方式寻找 Dependency

那么为什么找不到它呢?

PLUNKER

通知

我已经知道,如果我删除 @host - 它会到达顶部。我的问题是为什么添加 @host - 没有达到顶部 - 尽管 my-component3my-app !!

之下

我想知道@Optional() 是否正在注入 null。我相信一个人可能是罪魁祸首。

编辑

所以从你的 plunker 我似乎无法找到组件 3 的实际主机。像

   <parent-component>
       <component-3><component-3/>
<parent-component/>

根据我的理解here它似乎在寻找什么。

只需从您的组件 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 获取提供程序。

查看 A curios case of the @Host decorator and Element Injectors in Angular 以深入了解 @Host 装饰器的工作原理以及元素注入器的作用。

为了使其正常工作,您应该在父组件中定义依赖项并使用viewProviders:

@Component({
  selector: 'my-app',
  viewProviders: [{provide: Dependency, useValue: createDependency('AppModule provider')}],
    ...
export class MyApp {}

metadata.ts里面的评论是这样说的:

Specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component.

所以基本上它说在解析依赖项时不使用宿主元素注入器和以上的所有注入器。因此,如果您的 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将得到解决。

直接来自 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 装饰器的用例非常狭窄,只有在投影内容时才真正有意义。