@Self和@Host Angular 2+依赖注入装饰器之间的区别

时间:2017-05-01 23:41:35

标签: angular dependency-injection angular2-services angular-decorator

请解释@Self@Host之间的区别。

有角度的API文档提供了一些想法。但我不清楚。 为Self提供的示例使用ReflectiveInjector来举例说明用法。

然而,如果有的话,很少会在实际的应用代码中使用ReflectiveInjector(可能更多的是在测试中)。你能举例说明你将在哪里使用@Self 而不是< / strong> @Host在此类测试场景之外?

4 个答案:

答案 0 :(得分:11)

TL;博士

看起来当使用@Self时,Angular只会查找绑定在该指令/组件所在元素的组件注入器上的值。

看起来当使用@Host时,Angular将查找绑定在该指令/组件所在元素的组件注入器上或父组件的注入器上的值。 Angular将此父组件称为“主机”。

更多解释

虽然主要描述不是很有用,但看起来@Self@Host文档中的示例可以很好地说明它们的使用方式和区别(复制)下文)。

当试图理解这一点时,可能有助于记住,当Angular依赖注入尝试解析构造函数的特定值时,它首先查看注入器中的当前组件,然后通过父注入器向上迭代。这是因为Angular使用分层注入器并允许从祖先注入器继承。

因此,当@Host文档说“它指定一个注入器应该从任何注入器检索依赖关系,直到到达当前组件的主机元素”时,这意味着它一旦到达就会提前停止这个向上的迭代绑定到父组件的注入器。

@Self示例(source

class Dependency {}

@Injectable()
class NeedsDependency {
  constructor(@Self() public dependency: Dependency) {}
}

let inj = ReflectiveInjector.resolveAndCreate([Dependency, NeedsDependency]);
const nd = inj.get(NeedsDependency);

expect(nd.dependency instanceof Dependency).toBe(true);

inj = ReflectiveInjector.resolveAndCreate([Dependency]);
const child = inj.resolveAndCreateChild([NeedsDependency]);
expect(() => child.get(NeedsDependency)).toThrowError();

@Host示例(source

class OtherService {}
class HostService {}

@Directive({selector: 'child-directive'})
class ChildDirective {
  logs: string[] = [];

  constructor(@Optional() @Host() os: OtherService, @Optional() @Host() hs: HostService) {
    // os is null: true
    this.logs.push(`os is null: ${os === null}`);
    // hs is an instance of HostService: true
    this.logs.push(`hs is an instance of HostService: ${hs instanceof HostService}`);
  }
}

@Component({
  selector: 'parent-cmp',
  viewProviders: [HostService],
  template: '<child-directive></child-directive>',
})
class ParentCmp {
}

@Component({
  selector: 'app',
  viewProviders: [OtherService],
  template: '<parent-cmp></parent-cmp>',
})
class App {
}

使用@Self时的示例很重要

假设您有一个用于修改许多类型组件行为的指令;也许这个指令提供了某种配置支持。

此指令绑定到整个应用程序中的许多组件,此指令将某些服务绑定到其providers列表中。想要使用此指令动态配置自身的组件将注入它提供的服务。

但是,我们希望确保组件仅使用自己的配置,并且不会意外地注入针对某些父组件的配置服务。因此,我们使用@Self装饰器告诉Angular的依赖注入只考虑此组件元素上提供的配置服务。

答案 1 :(得分:3)

https://netbasal.com/exploring-the-various-decorators-in-angular-b208875b207c

主机:

  

@Host -   @Host装饰器告诉DI在任何进样器中查找依赖关系,直到它到达主机

自:

  

@Self-   @Self装饰器告诉DI只从它自己寻找一个依赖,所以它不会走向树

这是一个例子:

https://plnkr.co/edit/UmpPTnzcRxgDc9Hn5I9G?p=preview

如您所见,MyDir指令使用: @Self访问自己的汽车 它的组成部分&#39; @Host Garage依赖 @Optional @Host Sun依赖关系,它未在主机上定义,但在App上定义。由于它没有在Host上定义 - 它将为null

输出将是:

 parent component. 
  { "type": "child garage", 
    "car": { "model": "child car" }, 
    "sun": null 
  }

以下是组件和提供者:

  class Garage {
    car;
    type;
    sun;

    constructor(type) {
      this.type=type;
    }
    setCar(car) {
      this.car = car;
    }
    setSun(sun) {
      this.sun = sun;
    }
  }

  class Car {
    model;
    constructor(model) {
      this.model=model;
    }
  }

  class Sun { }

  @Directive({
    selector: '[myDir]',
    providers:[
      {provide: Car, useValue: new Car('child car')}
      {provide: Garage, useValue: new Garage('child garage')}
    ]
  })
  export class MyDir {
    constructor(@Self() private car: Car, @Host() private garage: Garage,
      @Optional() @Host() private sun: Sun) {
       this.garage.setCar(this.car);
       this.garage.setSun(this.sun);
    }
  }

  @Component({
    selector: 'parent',
    template: `
       parent component. {{garage|json}}
    `,
    providers:[
      {provide: Car, useValue: new Car('parent car')},
      {provide: Garage, useValue: new Garage('parent garage')}
    ]
  })
  export class Parent {
    childDep;
    constructor(private car: Car, private garage: Garage) {
    }
  }

  @Component({
    selector: 'my-app',
    template: `
  <parent myDir></parent>
    `,
    providers:[
      {provide: Car, useValue: new Car('app car')},
      {provide: Garage, useValue: new Garage('app garage')},
      {provide: Sun, useValue: 'sun'}
    ]
  })
  export class App {
  }

答案 2 :(得分:1)

Angular通过在元素注入器的层次结构中搜索依赖关系来解决依赖关系,该依赖关系从当前元素的注入器开始,然后在父元素的注入器中找不到,依此类推。如果仍未找到相关性,则它将移至模块注入器。如果在那里找不到,则会引发错误。 https://angular.io/guide/hierarchical-dependency-injection#host

@Self和@Host是修饰符,用于告知Angular应该停止寻找依赖的注入器。

@Self

@Self告诉Angular,它只应在当前元素上的注入器内查看。需要注意的重要一点是,每个元素只有一个注入器,该注入器由附加到它的每个指令共享。因此,在此模板代码段中:

<div dir-1 dir-2></div>

假设dir-1对应于Directive1,dir-2对应于Directive2, 如果Directive1注册了提供商,则Directive2将能够注入该服务,反之亦然。

如果依赖项具有@Self修饰符,则意味着Angular将仅在当前元素的注入器中查找提供者。除非还存在@Optional修饰符,否则如果找不到它,则会引发错误。

@Self的用例是,如果您希望将服务注入到指令或组件中,则前提是同一元素上的另一个指令提供了该服务。 (该指令显然可以提供服务本身,但这似乎使@Self的使用有点多余)。

证明

https://stackblitz.com/edit/angular-di-test-4-6jxjas?file=src%2Fapp%2Fapp.component.htmlapp.component.html

中考虑此模板
<div my-directive-alpha>
    <h1 my-directive-beta my-directive-gamma>Lorem Ipsum..</h1>
</div>

my-directive-alpha对应于MyDirectiveAlpha,my-directive-beta对应于MyDirectiveBeta,my-directive-gamma对应于MyDirectiveGamma。

当MyDirectiveGamma尝试注入MehProvider时:

  constructor(@Self() meh: MehProvider) {
    console.log("gamma directive constructor:", meh.name);
  }

MyDirectiveAlpha和MyDirectiveBeta都在其provider数组中配置MehProvider。 如果从模板中删除my-directive-beta,则会收到一条错误消息,提示Angular无法找到MehProvider。 如果然后从MyDirectiveGamma中删除@Self装饰器,Angular将从MyDirectiveAlpha中找到MehProvider。因此,@ Self修饰符将Angular限制为只能查看当前元素上的注入器。

@Host

@Host告诉Angular,它应该停止在注入程序之外为当前模板寻找提供程序。出于本文的目的,我将其称为模板注入器,但是Angular的文档未使用该术语。该注入器包含来自组件的 viewProviders 数组的那些提供程序。组件可能还具有一个 providers 数组,该数组配置了一个注射器,我将其称为 component injection

因此对于此组件:

<my-component></my-component>

使用此模板:

<div>
  <h2>my component</h2>
  <div my-dir-1>
    <div my-dir-2>lorem ipsum...</div>
  </div>
</div>

如果MyDirective2尝试注入使用@Host修饰符注释的依赖项,则假设my-dir-1对应于MyDirective1,而my-dir-2对应于MyDirective2:

constructor(@Host() foo: FooProvider) {
...
}

然后Angular将搜索所有元素注入器,直到元素树为止,但不会超出MyComponent的模板注入器。如果未找到提供程序,则再次假设不存在@Optional修饰符,则将引发错误。

即使提供者存在于组件注入器中,仍然会引发错误,因为Angular不会在那里搜索。因此,我们可以得出结论,组分注入器位于模板注入器之上。

@Host的用例是确保指令的包含组件可以控制如何注入特定服务。

证明

https://stackblitz.com/edit/angular-di-host-modifier-proof?file=src%2Fapp%2Fmy-component%2Fmy-component.component.ts

考虑MyComponent:

@Component({
  selector: "my-component",
  providers: [{provide: FooProvider, useValue: {name: 'FooProvider from providers'}}],
  viewProviders: [{provide: FooProvider, useValue: {name: 'FooProvider from view-providers'}}],
  template: `
    <div>
      <h2>This is my component</h2>
      <div>
        <h3 my-directive>Lorem Ipsum...</h3>
      </div>
    </div>
  `,

})
export class MyComponent {}

my-directive对应于MyDirective。 鉴于MyDirective尝试注入FooProvider并使用@Host修饰符:

  constructor(@Host() foo: FooProvider) {
    console.log("my directive:", foo.name);
  }

注入的FooProvider的实际实例是在viewProviders数组中的实例。如果我们注释掉该数组,则会收到一条错误消息,告诉我们Angular无法找到提供程序,即使它仍存在于providers数组中。因此,@ Host可以防止Angular向提供程序寻找组件的模板注入器。

答案 3 :(得分:0)

装饰器@Host()仅在上下文中使用组件和指令。它与装饰器@Self()为服务所做的工作类似。