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

Posted

技术标签:

【中文标题】Angular `@Host` 装饰器没有到达顶部?【英文标题】:Angular's `@Host` decorator not reaching the top? 【发布时间】:2018-05-16 02:36:03 【问题描述】:

在我的主要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) 

结果是:

Component3: Hello from 3 :

但我希望结果是:

Component3: Hello from 3 :AppModule 提供者

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

<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 之下!!

【问题讨论】:

【参考方案1】:

查看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 内部的 cmets 是这样说的:

指定注入器应该从任何 注入器直到到达当前组件的宿主元素。

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

【讨论】:

app标签下不是comp3吗? 也许我错过了一些东西,但主机元素是应用程序上的主要元素吗? @RoyiNamir 您在模块注入器(AppModule 提供程序数组)中声明了提供程序,而 AngularInDepth.com 告诉您在主机(应用程序组件)上声明视图提供程序 @RoyiNamir 我看到上面的所有答案 :) @RoyiNamir,首先,我不明白为什么它不会,因为它都在一个组件视图/模板中。其次,文章中的示例即使没有@Host装饰器也可以工作【参考方案2】:

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

编辑

所以从你的 plunker 看来,我似乎找不到组件 3 的实际主机。像

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

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

【讨论】:

确实如此。如果我删除它,我会收到错误消息。但我现在想忽略这个错误。事实上,它没有在其顶部找到任何提供者 - 这是罪魁祸首 - 因此我的问题。 我已经编辑了我的答案,并附上了一个可能解释的链接 App 是承载组件 3 的主要元素。为什么它不匹配呢? 从我提供的链接中,我相信您的问题是没有直接的父组件或指令。我相信@host 会寻找它。也许它比你最初想的要具体一些。但是有时间我会挖一点北斗【参考方案3】:

只需从 Component 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 中获取提供者。

【讨论】:

我已经知道了。我想了解为什么 HOST 没有登顶。 这不是真实的。 viewProviders(我没有使用)是一种告诉 ng-content 的方法 - 不知道主机组件的提供者。 似乎在使用 @Host() 装饰器时,Angular 会查看当前元素的所有父注入器。见单元测试here【参考方案4】:

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

【讨论】:

只有在投影内容时才有意义。 - 这不是真的,请参阅我对 @Host 装饰器的真实用例的回答 我认为我们之前已经讨论过 Angular 内部使用某些东西的方式从开发人员使用框架构建应用程序的角度来看并不总是(甚至很少)有意义。

以上是关于Angular `@Host` 装饰器没有到达顶部?的主要内容,如果未能解决你的问题,请参考以下文章

装饰器何时以及如何应用到 @angular 包中的装饰类

Angular 2 '@Component' 装饰器是不是总是需要元素名称选择器?

Angular 2 '@Component' 装饰器是不是总是需要元素名称选择器?

angular装饰器

Angular - 创建通用装饰器包装 @HostListener

Angular- 巧用@component装饰器属性