Difference between @Self and @Host Angular 2+ Dependency Injection Decorators

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

@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).

Proof

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

@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.

Proof

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.


tl;dr

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".

More explanation

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.

@Self example (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 example (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 {
}

Example of when using @Self is important

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.


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

Host:

@Host — The @Host decorator tells DI to look for a dependency in any injector until it reaches the host

Self:

@Self — The @Self decorator tells DI to look for a dependency only from itself, so it will not walk up the tree

here is an example:

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

As you see MyDir directive use: @Self to access its own Car Its component' @Host Garage dependency @Optional @Host Sun dependency that is not defined on Host but defined on App. Since it is not defined on Host - it will be null

Output will be:

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

Here is components and providers:

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