[Angular] Control the dependency lookup with @Host, @Self, @SkipSelf and @Optional

Very differently to AngularJS (v1.x), Angular now has a hierarchical dependency injector. That allows to specify service definitions as well as the service lifetime at various levels in our application. Whenever a service is requested, Angular will walk up the component tree and search for a matching definition. While in most cases that's perfectly fine, often you may want to take control over the dependency lookup. In this lesson we will learn how, by applying"@Host, @Self(), @SkipSelf() and @Optional().

@Optional:

When using @Optional, it set logger to null if the LoggerService is not provided instead of error out.

export class PersonComponent implements OnInit {
  constructor(
    @Optional()
    public logger: LoggerService
  ) {}

  ngOnInit() {}

  doLog() {
    if (this.logger) {
      this.logger.log('Message from PersonComponent');
    } else {
      console.log('sorry, no logger available');
    }
  }
}

@SkipSelf:

If child component and parent component both using the same provider and we want to skip using child component's provider instead using parent's provider.

// Child
@Component({
  selector: 'app-person',
  template: `
    <div >
      <p *ngIf="logger === null">I don't have a logger</p>
      <button (click)="doLog()">write log</button>
    </div>
  `
   providers: [
     {
       provide: LoggerService,
       useFactory: loggerFactory('PersonComponent')
     }
   ]
})
export class PersonComponent implements OnInit {
  constructor(
    @SkipSelf()
    @Optional()
    public logger: LoggerService
  ) {}

  ngOnInit() {}

  doLog() {
    if (this.logger) {
      this.logger.log('Message from PersonComponent');
    } else {
      console.log('sorry, no logger available');
    }
  }
}
// parent

@Component({
  selector: 'app-root',
  template: `
    <h1>Angular Services</h1>
    <app-person></app-person>
  `,
  providers: [
    {
      provide: LoggerService,
      useFactory: loggerFactory('AppComponent')
    }
  ]
})
export class AppComponent {}

SO in the end 'AppComponent ...' log message will appear in the console.

@Self():

Only using the provider for its own component.

@Component({
  selector: 'app-person',
  template: `
    <div >
      <p *ngIf="logger === null">I don't have a logger</p>
      <button (click)="doLog()">write log</button>
    </div>
  `
  // providers: [
  //   {
  //     provide: LoggerService,
  //     useFactory: loggerFactory('PersonComponent')
  //   }
  // ]
})
export class PersonComponent implements OnInit {
  constructor(
    @Self()
    @Optional()
    public logger: LoggerService
  ) {}

  ngOnInit() {}

  doLog() {
    if (this.logger) {
      this.logger.log('Message from PersonComponent');
    } else {
      console.log('sorry, no logger available');
    }
  }
}

So if PersonComponent has provider defined, it will use its own provider and will not continue searching parent component.

Often @Self can use togerther with @Optional, so if the provider is not defined, then set it to null.

@Host:

When we have directive, we might need to use @Host.

@Component({
  selector: 'my-comp',
  ...
  providers: [
     MyService // Must have, other directive cannot find it, throw error.
  ]  
})
<my-comp highlighted />
@Directive({
  selector: 'highlighted'
})
export class Hightlighted {
  // Use the provide inject directly into the host component
  constructor (@Host private myService: MyService) {}
}

Because we cannot ensure that host element must have the Injection, if not, Angular will throw error, to prevent that, @Host normally work with @Optional together.

@Directive({
  selector: 'highlighted'
})
export class Hightlighted {
  // Use the provide inject directly into the host component
  constructor (@Optional @Host private myService: MyService) {}
}

Lesson