Иерархия инжекторов. Видимость сервисов. Часть 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. Таким образом, в пользовательском интерфейсе отображается следующее:
В логическом дереве шаблона это выглядит так:
<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>.