angular
March 14

Иерархия инжекторов. Видимость сервисов. Часть 3. 

Эта часть описывает, как ограничить область видимости начального и конечного ElementInjector, используя декораторы видимости @Host(), @Self() и @SkipSelf().

Видимость инжектированных токенов

Декораторы влияют на то, где начинается и заканчивается поиск токена инъекции в логическом дереве. Для этого разместим декораторы видимости в конструкторе. Чтобы изменить место, где инжектор начинает искать FlowerService, добавьте @SkipSelf() к декларации @Inject для FlowerService в <app-child>. Это объявление находится в конструкторе <app-child>, как показано в child.component.ts:

constructor(@SkipSelf() public flower : FlowerService) { }

С @SkipSelf() инжектор <app-child> не ищет FlowerService в самом себе. Вместо этого, инжектор начинает поиск FlowerService в ElementInjector или <app-root>, где он не находит ничего. Затем он возвращается к ModuleInjector <app-child> и находит значение hibiscus 🌺, которое доступно, потому что ModuleInjector <app-child> и ModuleInjector <app-root> объединены в один ModuleInjector. Таким образом, в пользовательском интерфейсе отображается следующее:

Emoji from FlowerService: 🌺

В логическом дереве шаблона это выглядит так:

<app-root ApplicationConfig
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
    <app-child @Provide(FlowerService="🌻")>
      <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">
        <!-- With SkipSelf, the injector looks to the next injector up the tree -->
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

Хотя <app-child> сервис возвратит sunflower 🌻, в приложении отобразится hibiscus 🌺, потому что @SkipSelf() заставляет текущий инжектор пропустить себя и обратиться к своему родителю.

Если добавить @Host()(в дополнение к @SkipSelf()) к @Inject для FlowerService, результат будет null. Это потому, что @Host() ограничивает верхнюю границу поиска <#VIEW>. Вот как это выглядит в логическом дереве:

<app-root ApplicationConfig
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW> <!-- end search here with null-->
    <app-child @Provide(FlowerService="🌻")> <!-- start search here -->
      <#VIEW @Inject(FlowerService, @SkipSelf, @Host, @Optional)=>null>
      </#VIEW>
      </app-parent>
  </#VIEW>
</app-root>

Здесь сервисы и их значения одинаковы, но @Host() останавливает поиск инжектором дальше <#VIEW> для FlowerService, поэтому он не находит сервис и возвращает null.

@SkipSelf() в viewProviders

Текущий <app-child> предоставляет AnimalService в массиве viewProviders со значением dog🐶. Поскольку инжектор ищет AnimalService только в ElementInjector <app-child>, он никогда не видит whale🐳.

Как и в примере с FlowerService, если добавить @SkipSelf() в конструктор для AnimalService, инжектор не будет искать AnimalService в ElementInjector текущего <app-child>.

export class ChildComponent {

  // add @SkipSelf()
  constructor(@SkipSelf() public animal : AnimalService) { }

}

Вместо этого инжектор начнет с ElementInjector <app-root>. Помните, что класс <app-child> возвращает AnimalService в массиве viewProviders со значением собаки dog🐶:

@Component({
  standalone: true,
  selector: 'app-child',
  …
  viewProviders:
  [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
  ...
})

В логическом дереве шаблона это выглядит так:

<app-root ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW><!-- search begins here -->
    <app-child>
      <#VIEW @Provide(AnimalService="🐶")
             @Inject(AnimalService, SkipSelf=>"🐳")>
        <!--Add @SkipSelf -->
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

С использованием @SkipSelf() в <app-child> инжектор начинает поиск AnimalService в ElementInjector <app-root> и находит whale🐳.

@Host() в viewProviders

Если добавить @Host() в конструкторе AnimalService, результат будет dog🐶, потому что инжектор находит AnimalService в <#VIEW> <app-child>. Вот массив viewProviders в классе <app-child> и @Host() в конструкторе:

@Component({
  standalone: true,
  selector: 'app-child',
  …
  viewProviders:
  [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
  ...

})
export class ChildComponent {
  constructor(@Host() public animal : AnimalService) { }
}

@Host() заставляет инжектор искать до тех пор, пока он не достигнет края <#VIEW>.

<app-root ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW>
    <app-child>
      <#VIEW @Provide(AnimalService="🐶")
             @Inject(AnimalService, @Host=>"🐶")> <!-- @Host stops search here -->
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

Добавим массив viewProviders с hedgehog 🦔, в app.component.ts:

@Component({
  standalone: true,
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  viewProviders: [{ provide: AnimalService, useValue: { emoji: '🦔' } }]
  ...
})

Далее добавим @SkipSelf() вместе с @Host() в конструктор сервиса child.component.ts:

export class ChildComponent {
  constructor(
  @Host() @SkipSelf() public animal : AnimalService) { }
}

Когда @Host() и @SkipSelf() были применены к FlowerService, который находится в массиве providers, результатом было значение null, потому что @SkipSelf() начинает поиск внутри инжектора <app-child>, а @Host() прекращает поиск в <#VIEW> — где нет FlowerService. В логическом дереве видно, что FlowerService виден в <app-child>, а не в его <#VIEW>.

<app-root ApplicationConfig
        @Inject(AnimalService=>"🐳")>
  <#VIEW @Provide(AnimalService="🦔")
         @Inject(AnimalService, @Optional)=>"🦔">
    <!-- ^^@SkipSelf() starts here,  @Host() stops here^^ -->
    <app-child>
      <#VIEW @Provide(AnimalService="🐶")
             @Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"🦔">
               <!-- Add @SkipSelf ^^-->
      </#VIEW>
      </app-child>
  </#VIEW>
</app-root>

@SkipSelf() заставляет инжектор начинать поиск AnimalService в <app-root>, а не в <app-child>, откуда происходит запрос, и @Host() останавливает поиск в <app-root> <#VIEW>. Поскольку AnimalService объявлен с помощью массива viewProviders, инжектор находит hedgehog 🦔 в <#VIEW>.