@Self 和@Host 之间的区别 Angular 2+ 依赖注入装饰器
Difference between @Self and @Host Angular 2+ Dependency Injection Decorators
的区别
angular API 文档提供了一些想法。但我不清楚。
为 Self
提供的示例使用 ReflectiveInjector
来举例说明用法。
但是,人们很少(如果有的话)在实际应用程序代码中使用 ReflectiveInjector
(在测试中可能更多)。您能举例说明在何处使用 @Self
而不是 of @Host
在这样的测试场景之外??
https://netbasal.com/exploring-the-various-decorators-in-angular-b208875b207c
主持人:
@Host —
The @Host decorator tells DI to look for a dependency in any injector until it reaches the host
自己:
@Self —
The @Self decorator tells DI to look for a dependency only from itself, so it will not walk up the tree
这里有一个例子:
https://plnkr.co/edit/UmpPTnzcRxgDc9Hn5I9G?p=preview
如您所见,MyDir 指令使用:
@Self 访问自己的Car
它的组件' @Host Garage 依赖
@Optional @Host 未在Host 上定义但在App 上定义的Sun 依赖项。由于它未在主机上定义 - 它将为 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 {
}
tl;博士
看起来当使用 @Self
时,Angular 只会查找绑定在组件注入器上的值,用于此 Directive/Component 所在的元素。
看起来当使用 @Host
时,Angular 将查找绑定在该 Directive/Component 所在元素的组件注入器上的值,或parent 组件的注入器。 Angular 将此 parent 组件称为 "host"。
更多解释
虽然主要描述不是很有帮助,但看起来 @Self and @Host 文档中的示例很好地阐明了它们的使用方式和区别(复制如下)。
当试图理解这一点时,记住当 Angular 依赖注入试图解析构造函数的特定值时,它首先在当前组件的注入器中查找,然后迭代通过 parent 个喷油器向上。这是因为 Angular 使用分层注入器并允许从祖先注入器继承。
因此,当 @Host
文档说它 "specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component" 时,这意味着一旦到达绑定到 parent 组件的注入器,它就会提前停止向上迭代。
@自我例子(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
列表中的一些服务。想要使用这个指令动态配置自己的组件将注入它提供的服务。
但是,我们要确保一个组件只使用它自己的配置,而不是意外地注入用于某些 parent 组件的配置服务。所以我们使用 @Self
装饰器告诉 Angular 的依赖注入只考虑在这个组件的元素上提供的配置服务。
装饰器@Host()
仅在组件和指令的上下文中使用。它与装饰器 @Self()
为服务所做的工作类似。
Angular 通过在元素注入器的层次结构中搜索它们来解决依赖关系,从当前元素的注入器开始,然后如果找不到父元素则移动到父元素的注入器,依此类推。如果仍未找到依赖项,它将移至模块注入器。如果在那里找不到,则会抛出错误。 https://angular.io/guide/hierarchical-dependency-injection#host
@Self 和@Host 是修饰符,告诉 Angular 它应该停止寻找依赖项的注入器。
@自我
@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.html
在 app.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 都在其提供程序数组中配置 MehProvider。
如果您从模板中删除 my-directive-beta,您将收到一条错误消息,指出 Angular 找不到 MehProvider。
如果您随后从 MyDirectiveGamma 中删除 @Self 装饰器,Angular 将从 MyDirectiveAlpha 中找到 MehProvider。因此,@Self 修饰符将 Angular 限制为查看当前元素上的注入器。
@主持人
@Host 告诉 Angular 它应该停止在当前模板的注入器之外寻找提供者。出于本文的目的,我将其称为 模板注入器 ,但 Angular 的文档不使用该术语。该注入器包含组件的 viewProviders 数组中的那些提供程序。一个组件也可能有一个 providers 数组,它配置了一个注入器,我称之为 component injector.
所以对于这个组件:
<my-component></my-component>
使用此模板:
<div>
<h2>my component</h2>
<div my-dir-1>
<div my-dir-2>lorem ipsum...</div>
</div>
</div>
假设my-dir-1
对应MyDirective1,my-dir-2
对应MyDirective2,如果MyDirective2试图注入一个用@Host修饰符注解的依赖:
constructor(@Host() foo: FooProvider) {
...
}
然后 Angular 将在元素树中向上搜索所有元素注入器,但不会超出 MyComponent 的模板注入器。如果未找到提供者,再次假设 @Optional 修饰符不存在,则会抛出错误。
即使提供者存在于组件注入器中,仍然会抛出错误,因为 Angular 不会在那里搜索。因此我们可以得出结论,组件注入器比模板注入器高一级。
@Host 的用例是确保指令的包含组件可以控制特定服务的注入方式。
证明
考虑我的组件:
@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 找不到提供者,即使它仍然存在于提供者数组中。因此 @Host 阻止 Angular 超越提供者组件的模板注入器。
@self 可以使用 directive/component self 提供者。但主机不是
@Directive({
selector: '[myDir]',
providers:[
{provide: Car, useValue: new Car('child car')}
]
})
export class Parent {
constructor(@Self() private car: Car){}
}
angular API 文档提供了一些想法。但我不清楚。
为 Self
提供的示例使用 ReflectiveInjector
来举例说明用法。
但是,人们很少(如果有的话)在实际应用程序代码中使用 ReflectiveInjector
(在测试中可能更多)。您能举例说明在何处使用 @Self
而不是 of @Host
在这样的测试场景之外??
https://netbasal.com/exploring-the-various-decorators-in-angular-b208875b207c
主持人:
@Host — The @Host decorator tells DI to look for a dependency in any injector until it reaches the host
自己:
@Self — The @Self decorator tells DI to look for a dependency only from itself, so it will not walk up the tree
这里有一个例子:
https://plnkr.co/edit/UmpPTnzcRxgDc9Hn5I9G?p=preview
如您所见,MyDir 指令使用: @Self 访问自己的Car 它的组件' @Host Garage 依赖 @Optional @Host 未在Host 上定义但在App 上定义的Sun 依赖项。由于它未在主机上定义 - 它将为 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 {
}
tl;博士
看起来当使用 @Self
时,Angular 只会查找绑定在组件注入器上的值,用于此 Directive/Component 所在的元素。
看起来当使用 @Host
时,Angular 将查找绑定在该 Directive/Component 所在元素的组件注入器上的值,或parent 组件的注入器。 Angular 将此 parent 组件称为 "host"。
更多解释
虽然主要描述不是很有帮助,但看起来 @Self and @Host 文档中的示例很好地阐明了它们的使用方式和区别(复制如下)。
当试图理解这一点时,记住当 Angular 依赖注入试图解析构造函数的特定值时,它首先在当前组件的注入器中查找,然后迭代通过 parent 个喷油器向上。这是因为 Angular 使用分层注入器并允许从祖先注入器继承。
因此,当 @Host
文档说它 "specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component" 时,这意味着一旦到达绑定到 parent 组件的注入器,它就会提前停止向上迭代。
@自我例子(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
列表中的一些服务。想要使用这个指令动态配置自己的组件将注入它提供的服务。
但是,我们要确保一个组件只使用它自己的配置,而不是意外地注入用于某些 parent 组件的配置服务。所以我们使用 @Self
装饰器告诉 Angular 的依赖注入只考虑在这个组件的元素上提供的配置服务。
装饰器@Host()
仅在组件和指令的上下文中使用。它与装饰器 @Self()
为服务所做的工作类似。
Angular 通过在元素注入器的层次结构中搜索它们来解决依赖关系,从当前元素的注入器开始,然后如果找不到父元素则移动到父元素的注入器,依此类推。如果仍未找到依赖项,它将移至模块注入器。如果在那里找不到,则会抛出错误。 https://angular.io/guide/hierarchical-dependency-injection#host
@Self 和@Host 是修饰符,告诉 Angular 它应该停止寻找依赖项的注入器。
@自我
@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.html
在 app.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 都在其提供程序数组中配置 MehProvider。 如果您从模板中删除 my-directive-beta,您将收到一条错误消息,指出 Angular 找不到 MehProvider。 如果您随后从 MyDirectiveGamma 中删除 @Self 装饰器,Angular 将从 MyDirectiveAlpha 中找到 MehProvider。因此,@Self 修饰符将 Angular 限制为查看当前元素上的注入器。
@主持人
@Host 告诉 Angular 它应该停止在当前模板的注入器之外寻找提供者。出于本文的目的,我将其称为 模板注入器 ,但 Angular 的文档不使用该术语。该注入器包含组件的 viewProviders 数组中的那些提供程序。一个组件也可能有一个 providers 数组,它配置了一个注入器,我称之为 component injector.
所以对于这个组件:
<my-component></my-component>
使用此模板:
<div>
<h2>my component</h2>
<div my-dir-1>
<div my-dir-2>lorem ipsum...</div>
</div>
</div>
假设my-dir-1
对应MyDirective1,my-dir-2
对应MyDirective2,如果MyDirective2试图注入一个用@Host修饰符注解的依赖:
constructor(@Host() foo: FooProvider) {
...
}
然后 Angular 将在元素树中向上搜索所有元素注入器,但不会超出 MyComponent 的模板注入器。如果未找到提供者,再次假设 @Optional 修饰符不存在,则会抛出错误。
即使提供者存在于组件注入器中,仍然会抛出错误,因为 Angular 不会在那里搜索。因此我们可以得出结论,组件注入器比模板注入器高一级。
@Host 的用例是确保指令的包含组件可以控制特定服务的注入方式。
证明
考虑我的组件:
@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 找不到提供者,即使它仍然存在于提供者数组中。因此 @Host 阻止 Angular 超越提供者组件的模板注入器。
@self 可以使用 directive/component self 提供者。但主机不是
@Directive({
selector: '[myDir]',
providers:[
{provide: Car, useValue: new Car('child car')}
]
})
export class Parent {
constructor(@Self() private car: Car){}
}