Kindly explain the difference between @Self
and @Host
.
The angular API documentation gives some idea. But it's not clear to me.
The example provided for Self
uses ReflectiveInjector
to exemplify usage.
However, one would rarely, if ever, use ReflectiveInjector
in actual app code (probably more in testing).. Can you give an example of where you would use @Self
instead of @Host
outside of such test scenarios??
The @Self decorator instructs Angular to look for the dependency only in the local injector. The local injector is the injector that is part of the current component or directive.
The definition of @Host from the Angular Docs. Parameter decorator on a view-provider parameter of a class constructor that tells the DI framework to resolve the view by checking injectors of child elements, and stop when reaching the host element of the current component.
SkipSelflink Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the parent injector. Resolution works upward through the injector hierarchy, so the local injector is not checked for a provider.
OptionallinkParameter decorator to be used on constructor parameters, which marks the parameter as being an optional dependency. The DI framework provides null if the dependency is not found.
It looks like when @Self
is used, Angular will only look for a value that is bound on the component injector for the element that this Directive/Component exists on.
It looks like when @Host
is used, Angular will look for a value that is bound on either the component injector for the element that this Directive/Component exists on, or on the injector of the parent component. Angular calls this parent component the "host".
Although the main descriptions aren't very helpful, it looks like the examples in the documentation for @Self and @Host do a decent job of clarifying how they are used and what the difference is (copied below).
When trying to understand this, it might help to remember that when Angular dependency injection tries to resolve a particular value for a constructor, it starts by looking in the injector for the current component, then it iterates upward through parent injectors. This is because Angular uses hierarchical injectors and allows for inheritance from ancestor injectors.
So when the @Host
documentation says that it "specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component", that means that it stops this upward iteration early once it reaches the injector bound to the parent component.
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();
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 {
}
Let's say you have a directive that is used to modify the behavior of many types of components; maybe this directive provides some sort of configuration support.
This directive is bound to many components throughout your app and this directive binds some service in its providers
list. Components that want to use this directive to dynamically configure themselves will inject the service it provides.
However, we want to make sure that a component only uses its own configuration, and doesn't accidentally inject the configuration service that was meant for some parent component. So we use the @Self
decorator to tell Angular's dependency injection to only consider the configuration service provided on this component's element.
Angular resolves dependencies by searching for them within the hierarchy of element injectors starting on the injector for the current element, then moving onto that for the parent element if it's not found there, and so on. If the dependency is still not found, it moves onto the module injectors. If it isn't found there, an error is thrown. https://angular.io/guide/hierarchical-dependency-injection#host
@Self and @Host are modifiers that tell Angular on which injectors it should stop looking for dependencies.
@Self tells Angular that it should only look within the injector on the current element. An important point to note regarding this is that every element has just one injector that is shared by every directive that is attached to it. Thus, in this template snippet:
<div dir-1 dir-2></div>
Assuming that dir-1
corresponds to Directive1, and dir-2
corresponds to Directive2,
if Directive1 registers a provider, then Directive2 will be able to inject that service, and vice-versa.
If a dependency has the @Self modifier, this means that Angular will only look within the current element's injector for a provider. Unless the @Optional modifier is also present, an error will be thrown if it can't find it.
The use-case for @Self is if you want a service to be injected into a directive, or component, only if another directive on the same element provides it. (The directive can obviously supply the service itself, but that seems to make the use of @Self a bit redundant).
https://stackblitz.com/edit/angular-di-test-4-6jxjas?file=src%2Fapp%2Fapp.component.html
Consider this template in app.component.html
<div my-directive-alpha>
<h1 my-directive-beta my-directive-gamma>Lorem Ipsum..</h1>
</div>
Let my-directive-alpha
correspond to MyDirectiveAlpha, my-directive-beta
correspond to MyDirectiveBeta, and my-directive-gamma
to MyDirectiveGamma.
When MyDirectiveGamma attempts to inject MehProvider:
constructor(@Self() meh: MehProvider) {
console.log("gamma directive constructor:", meh.name);
}
Both MyDirectiveAlpha and MyDirectiveBeta configure MehProvider within their providers array. If you delete my-directive-beta from the template, you'll get an error saying that Angular can't find MehProvider. If you then remove the @Self decorator from MyDirectiveGamma, Angular will find MehProvider from within the MyDirectiveAlpha. Thus, the @Self modifier restricts Angular to looking at the injector on the current element.
@Host tells Angular that it should stop looking for providers beyond the injector for the current template. For the purposes of this article, I call this the template injector, but Angular's documentation does not use this term. This injector contains those providers from the viewProviders array of the component. A component may also have a providers array, which configures an injector that I will call the component injector.
So for this component:
<my-component></my-component>
With this template:
<div>
<h2>my component</h2>
<div my-dir-1>
<div my-dir-2>lorem ipsum...</div>
</div>
</div>
Assuming my-dir-1
corresponds to MyDirective1, and my-dir-2
corresponds to MyDirective2, if MyDirective2 attempts to inject a dependency annotated with the @Host modifier:
constructor(@Host() foo: FooProvider) {
...
}
Then Angular will search through all element injectors up through the tree of elements, but not go beyond the template injector of MyComponent. If the provider is not found, again assuming that the @Optional modifier is not present, then an error will be thrown.
An error will still be thrown even if the provider exists within the component injector because Angular will not search there. Thus we can conclude that the component injector is a level above the template injector.
The use case for @Host is to ensure that the containing component of a directive has control of how a particular service is injected.
https://stackblitz.com/edit/angular-di-host-modifier-proof?file=src%2Fapp%2Fmy-component%2Fmy-component.component.ts
Consider 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 {}
Let my-directive
correspond to MyDirective.
Given that MyDirective attempts to inject FooProvider and uses the @Host modifier:
constructor(@Host() foo: FooProvider) {
console.log("my directive:", foo.name);
}
The actual instance of FooProvider that is injected is that from within viewProviders array. If we comment out this array, we get an error that tells us Angular cannot find the provider, even though it still exists within the providers array. Thus @Host prevents Angular from looking beyond the template injector of a component for providers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With