Иерархические инжекторы¶
28.02.2022
Инжекторы в Angular имеют правила, которые вы можете использовать для достижения желаемой видимости инжекторов в ваших приложениях. Понимая эти правила, вы можете определить, в каком NgModule
, Component
или Directive
вам следует объявить провайдер.
В этой теме используются следующие пиктограммы.
html entities | pictographs |
---|---|
🌺 | red hibiscus (🌺 ) |
🌻 | sunflower (🌻 ) |
🌷 | tulip (🌷 ) |
🌿 | fern (🌿 ) |
🍁 | maple leaf (🍁 ) |
🐳 | whale (🐳 ) |
🐶 | dog (🐶 ) |
🦔 | hedgehog (🦔 ) |
Приложения, которые вы создаете с помощью Angular, могут стать довольно большими, и одним из способов управления этой сложностью является разделение приложения на множество небольших хорошо инкапсулированных модулей, которые сами по себе разделены на четко определенное дерево компонентов.
На вашей странице могут быть разделы, которые работают совершенно независимо от остальной части приложения, со своими локальными копиями служб и других зависимостей, которые им необходимы. Некоторые из сервисов, которые используют эти разделы приложения, могут быть общими с другими частями приложения или с родительскими компонентами, которые находятся дальше по дереву компонентов, в то время как другие зависимости должны быть частными.
С помощью иерархической инъекции зависимостей вы можете изолировать разделы приложения и предоставить им свои собственные частные зависимости, не разделяемые с остальной частью приложения, или заставить родительские компоненты разделять определенные зависимости только с дочерними компонентами, но не с остальной частью дерева компонентов, и так далее. Иерархическая инъекция зависимостей позволяет вам обмениваться зависимостями между различными частями приложения только тогда, когда и если это необходимо.
Типы иерархий инжекторов¶
Инжекторы в Angular имеют правила, которые вы можете использовать для достижения желаемой видимости инжекторов в ваших приложениях.
Понимая эти правила, вы можете определить, в каком NgModule
, Component
или Directive
вы должны объявить провайдер.
Angular имеет две иерархии инжекторов:
Иерархии инжекторов | Подробности |
---|---|
Иерархия ModuleInjector | Настройте ModuleInjector в этой иерархии, используя аннотацию @NgModule() или @Injectable() . |
Иерархия ElementInjector | Создается неявно в каждом элементе DOM. По умолчанию ElementInjector пуст, если вы не настроили его в свойстве providers в @Directive() или @Component() . |
ModuleInjector¶
Инжектор модулей можно настроить одним из двух способов, используя:
- Свойство
@Injectable()
providedIn
для ссылки на@NgModule()
, илиroot
. - Массив
@NgModule()
providers
.
Tree-shaking и @Injectable()
Использование свойства @Injectable()
providedIn
предпочтительнее, чем использование массива @NgModule()
providers
. Используя @Injectable()
providedIn
, инструменты оптимизации могут выполнять древовидную встряску, которая удаляет сервисы, не используемые вашим приложением. Это приводит к уменьшению размера пакета.
Tree-shaking особенно полезен для библиотеки, поскольку приложение, использующее библиотеку, может не иметь необходимости в ее инжектировании. Подробнее о tree-shakable providers читайте в Введение в сервисы и инъекцию зависимостей.
ModuleInjector
конфигурируется свойствами @NgModule.providers
и NgModule.imports
. ModuleInjector
— это уплощение всех массивов провайдеров, которые можно получить, рекурсивно следуя за NgModule.imports
.
Дочерние иерархии ModuleInjector
создаются при ленивой загрузке других @NgModules
.
Предоставляйте сервисы с помощью свойства providedIn
из @Injectable()
следующим образом:
1 2 3 4 5 6 7 8 |
|
Декоратор @Injectable()
идентифицирует класс сервиса. Свойство providedIn
настраивает определенный ModuleInjector
, здесь root
, что делает сервис доступным в root
ModuleInjector
.
Инжектор платформы¶
Есть еще два инжектора над root
, дополнительный ModuleInjector
и NullInjector()
.
Рассмотрим, как Angular загружает приложение, используя следующее в файле main.ts
:
1 |
|
Метод bootstrapModule()
создает дочерний инжектор инжектора платформы, который конфигурируется AppModule
. Это корневой
ModuleInjector
.
Метод platformBrowserDynamic()
создает инжектор, настроенный PlatformModule
, который содержит специфические для платформы зависимости. Это позволяет нескольким приложениям совместно использовать конфигурацию платформы.
Например, браузер имеет только одну строку URL, независимо от того, сколько приложений у вас запущено.
Вы можете настроить дополнительные провайдеры на уровне платформы, предоставив extraProviders
с помощью функции platformBrowser()
.
Следующим родительским инжектором в иерархии является NullInjector()
, который является вершиной дерева. Если вы забрались так далеко вверх по дереву, что ищете сервис в NullInjector()
, вы получите ошибку, если только не использовали @Optional()
, потому что в конечном итоге все заканчивается на NullInjector()
и он возвращает ошибку или, в случае @Optional()
, null
.
Более подробную информацию о @Optional()
смотрите в разделе @Optional()
этого руководства.
Следующая диаграмма представляет отношения между корневым
ModuleInjector
и его родительскими инжекторами, как описано в предыдущих параграфах.
Хотя имя root
является специальным псевдонимом, другие иерархии ModuleInjector
не имеют псевдонимов. У вас есть возможность создавать иерархии ModuleInjector
всякий раз, когда создается динамически загружаемый компонент, например, Router, который будет создавать дочерние иерархии ModuleInjector
.
Все запросы направляются к корневому инжектору, независимо от того, настроили ли вы его с помощью метода bootstrapModule()
или зарегистрировали все провайдеры с root
в своих собственных сервисах.
@Injectable()
vs. @NgModule()
Если вы настраиваете провайдера для всего приложения в @NgModule()
из AppModule
, он переопределяет провайдера, настроенного для root
в метаданных @Injectable()
. Вы можете сделать это, чтобы сконфигурировать провайдера не по умолчанию для сервиса, который совместно используется несколькими приложениями.
Вот пример случая, когда в конфигурацию маршрутизатора компонента включена стратегия размещения не по умолчанию location strategy путем перечисления его провайдера в списке providers
модуля AppModule
.
1 2 3 4 5 6 |
|
ElementInjector¶
Angular создает иерархии ElementInjector
неявно для каждого элемента DOM.
Предоставление сервиса в декораторе @Component()
с помощью свойства providers
или viewProviders
настраивает ElementInjector
. Например, следующий TestComponent
конфигурирует ElementInjector
, предоставляя сервис следующим образом:
1 2 3 4 5 |
|
Смотрите раздел правила разрешения, чтобы понять взаимосвязь между деревом ModuleInjector
и деревом ElementInjector
.
Когда вы предоставляете услуги в компоненте, эта услуга доступна через ElementInjector
в данном экземпляре компонента. Он также может быть виден в дочерних компонентах/директивах на основании правил видимости, описанных в разделе Правила разрешения.
Когда экземпляр компонента уничтожается, уничтожается и экземпляр сервиса.
@Directive()
и @Component()
.¶
Компонент — это особый тип директивы, а это значит, что так же, как @Directive()
имеет свойство providers
, @Component()
тоже имеет его. Это означает, что директивы, как и компоненты, могут настраивать провайдеров, используя свойство providers
.
Когда вы настраиваете провайдера для компонента или директивы с помощью свойства providers
, этот провайдер принадлежит ElementInjector
этого компонента или директивы.
Компоненты и директивы на одном и том же элементе имеют общий инжектор.
Правила разрешения¶
При разрешении токена для компонента/директивы, Angular разрешает его в два этапа:
- По отношению к родителям в иерархии
ElementInjector
. - По отношению к родителям в иерархии
ModuleInjector
.
Когда компонент объявляет зависимость, Angular пытается удовлетворить эту зависимость с помощью собственного ElementInjector
. Если инжектору компонента не хватает провайдера, он передает запрос вверх к ElementInjector
своего родительского компонента.
Запросы продолжают передаваться до тех пор, пока Angular не найдет инжектор, способный обработать запрос, или пока не закончатся иерархии предков ElementInjector
.
Если Angular не находит провайдера ни в одной иерархии ElementInjector
, он возвращается к элементу, откуда пришел запрос, и ищет его в иерархии ModuleInjector
. Если Angular все еще не находит провайдера, он выдает ошибку.
Если вы зарегистрировали провайдера для одного и того же DI-токена на разных уровнях, то для разрешения зависимости Angular использует первый встреченный провайдер. Если, например, провайдер зарегистрирован локально в компоненте, которому нужен сервис,
Angular не будет искать другого провайдера той же услуги.
Модификаторы разрешения¶
Поведение Angular при разрешении может быть изменено с помощью @Optional()
, @Self()
, @SkipSelf()
и @Host()
. Импортируйте каждый из них из @angular/core
и используйте каждый в конструкторе класса компонента, когда вы внедряете свой сервис.
Рабочее приложение, демонстрирующее модификаторы разрешения, которые рассматриваются в этом разделе, смотрите в примере модификаторов разрешения.
Типы модификаторов¶
Модификаторы разрешения делятся на три категории:
- Что делать, если Angular не находит то, что вы ищете, то есть
@Optional()
. - Где начать поиск, то есть
@SkipSelf()
. - Где остановить поиск,
@Host()
и@Self()
.
По умолчанию Angular всегда начинает с текущего Injector
и продолжает поиск по всему пути вверх. Модификаторы позволяют изменять начальное, или self, местоположение и конечное местоположение.
Кроме того, вы можете комбинировать все модификаторы, кроме @Host()
и @Self()
и, конечно, @SkipSelf()
и @Self()
.
@Optional()¶
@Optional()
позволяет Angular считать сервис, который вы вводите, необязательным. Таким образом, если он не может быть разрешен во время выполнения, Angular разрешает его как null
, а не выдает ошибку.
В следующем примере сервис OptionalService
не предоставляется ни в сервисе, ни в @NgModule()
, ни в классе компонента, поэтому он недоступен нигде в приложении.
1 2 3 4 5 |
|
@Self()¶
Используйте @Self()
, чтобы Angular просматривал ElementInjector
только для текущего компонента или директивы.
Хорошим вариантом использования @Self()
является внедрение сервиса, но только если он доступен для текущего элемента хоста. Чтобы избежать ошибок в этой ситуации, комбинируйте @Self()
с @Optional()
.
Например, в следующем SelfComponent
обратите внимание на инжектированный LeafService
в конструкторе.
1 2 3 4 5 6 7 8 9 10 |
|
В этом примере есть родительский провайдер, и инжектирование сервиса вернет значение, однако инжектирование сервиса с @Self()
и @Optional()
вернет null
, поскольку @Self()
говорит инжектору прекратить поиск в текущем хост-элементе.
Другой пример показывает класс компонента с провайдером для FlowerService
. В этом случае инжектор не смотрит дальше текущего ElementInjector
, потому что он находит FlowerService
и возвращает тюльпан 🌷.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
@SkipSelf()¶
@SkipSelf()
является противоположностью @Self()
. При использовании @SkipSelf()
, Angular начинает поиск сервиса в родительском ElementInjector
, а не в текущем.
Так, если родительский ElementInjector
использует значение fern 🌿 для emoji
, но у вас есть maple leaf 🍁 в массиве providers
компонента, Angular будет игнорировать maple leaf 🍁 и использовать fern 🌿.
Чтобы увидеть это в коде, предположим, что следующее значение для emoji
— это то, что использовал родительский компонент, как в этом сервисе:
1 2 3 |
|
Представьте, что в дочернем компоненте у вас есть другое значение, maple leaf 🍁 но вы хотите использовать значение родительского компонента. В этом случае вы используете @SkipSelf()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
В этом случае значение, которое вы получите для emoji
, будет папоротник 🌿, а не кленовый лист 🍁.
@SkipSelf() с @Optional()¶
Используйте @SkipSelf()
с @Optional()
для предотвращения ошибки, если значение равно null
. В следующем примере сервис Person
инжектируется в конструкторе.
@SkipSelf()
указывает Angular пропустить текущий инжектор, а @Optional()
предотвратит ошибку, если сервис Person
окажется null
.
1 2 3 |
|
@Host()¶
@Host()
позволяет вам назначить компонент последней остановкой в дереве инжекторов при поиске провайдеров. Даже если есть экземпляр сервиса дальше по дереву, Angular не будет продолжать поиск.
Используйте @Host()
следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Поскольку HostComponent
имеет @Host()
в своем конструкторе, независимо от того, что родитель HostComponent
может иметь в качестве значения flower.emoji
, HostComponent
будет использовать тюльпан 🌷.
Логическая структура шаблона¶
Когда вы предоставляете сервисы в классе компонента, сервисы становятся видимыми в дереве ElementInjector
относительно того, где и как вы предоставляете эти сервисы.
Понимание основной логической структуры шаблона Angular даст вам основу для настройки сервисов и, в свою очередь, управления их видимостью.
Компоненты используются в ваших шаблонах, как в следующем примере:
1 2 3 |
|
Обычно компоненты и их шаблоны объявляются в отдельных файлах. Для понимания того, как работает система инъекций, полезно рассмотреть их с точки зрения объединенного логического дерева.
Термин логическое отличает его от дерева рендеринга, которое является DOM-деревом вашего приложения.
Для обозначения мест расположения шаблонов компонентов в данном руководстве используется псевдоэлемент <#VIEW>
, который на самом деле не существует в дереве рендеринга и присутствует только в целях ментальной модели.
Ниже приведен пример того, как деревья представлений <app-root>
и <app-child>
объединяются в одно логическое дерево:
1 2 3 4 5 6 7 8 9 |
|
Понимание идеи демаркации <#VIEW>
особенно важно, когда вы настраиваете сервисы в классе компонента.
Предоставление сервисов в @Компонент()
¶
То, как вы предоставляете сервисы с помощью декоратора @Component()
(или @Directive()
), определяет их видимость. В следующих разделах демонстрируются providers
и viewProviders
, а также способы изменения видимости сервисов с помощью @SkipSelf()
и @Host()
.
Класс компонента может предоставлять услуги двумя способами:
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
Чтобы понять, как providers
и viewProviders
по-разному влияют на видимость сервиса, в следующих разделах шаг за шагом строится пример и сравнивается использование providers
и viewProviders
в коде и логическом дереве.
В логическом дереве вы увидите @Provide
, @Inject
и @NgModule
, которые не являются настоящими атрибутами HTML, но находятся здесь для демонстрации того, что происходит под капотом.
Атрибут службы Angular | Подробности |
---|---|
@Inject(Token)=>Value | Демонстрирует, что если Token внедрить в это место логического дерева, то его значением будет Value . |
@Provide(Token=Value) | Демонстрирует, что в данном месте логического дерева существует объявление провайдера Token со значением Value . |
@NgModule(Token) | Демонстрирует, что в этом месте должен использоваться инжектор NgModule . |
Пример структуры приложения¶
В примере приложения есть FlowerService
, предоставленный в root
со значением emoji
красного гибискуса 🌺.
1 2 3 4 5 6 |
|
Рассмотрим приложение, в котором есть только AppComponent
и ChildComponent
. Самый базовый визуализированный вид будет выглядеть как вложенные HTML-элементы, как показано ниже:
1 2 3 4 5 6 |
|
Однако за кулисами Angular использует логическое представление представления следующим образом при разрешении инъекционных запросов:
1 2 3 4 5 6 7 8 |
|
Здесь <#VIEW>
представляет собой экземпляр шаблона. Обратите внимание, что каждый компонент имеет свой собственный <#VIEW>
.
Знание этой структуры может помочь вам понять, как вы предоставляете и внедряете свои службы, и дать вам полный контроль над видимостью служб.
Теперь рассмотрим, что <app-root>
инжектирует FlowerService
:
1 2 3 |
|
Добавьте привязку к шаблону <app-root>
для визуализации результата:
1 |
|
Вывод в представлении будет следующим:
1 |
|
В логическом дереве это будет представлено следующим образом:
1 2 3 4 5 6 7 8 9 10 |
|
Когда <app-root>
запрашивает FlowerService
, задача инжектора — разрешить токен FlowerService
. Разрешение токена происходит в два этапа:
-
Инжектор определяет начальное местоположение в логическом дереве и конечное местоположение поиска.
Инжектор начинает с начального места и ищет токен на каждом уровне логического дерева.
Если маркер найден, он возвращается.
-
Если токен не найден, инжектор ищет ближайшего родителя
@NgModule()
для передачи запроса.
В примере ограничения следующие:
-
Начинается с
<#VIEW>
, принадлежащего<app-root>
и заканчивается<app-root>
.-
Обычно начальная точка поиска находится в точке внедрения.
Однако в данном случае
<app-root>
@Component
являются особенными, поскольку они также включают свои собственныеviewProviders
, поэтому поиск начинается с<#VIEW>
, принадлежащего<app-root>
.Это не относится к директиве, сопоставленной в том же месте.
-
Конечное местоположение совпадает с местоположением самого компонента, поскольку он является самым верхним компонентом в этом приложении.
-
-
Модуль
AppModule
действует как запасной инжектор, когда инжектируемый токен не может быть найден в иерархииElementInjector
.
Использование массива providers
.¶
Теперь в классе ChildComponent
добавьте провайдер для FlowerService
, чтобы продемонстрировать более сложные правила разрешения в последующих разделах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Теперь, когда FlowerService
предоставлен в декораторе @Component()
, когда <app-child>
запрашивает сервис, инжектору достаточно посмотреть до ElementInjector
в <app-child>
. Ему не придется продолжать поиск дальше по дереву инжекторов.
Следующим шагом будет добавление привязки к шаблону ChildComponent
.
1 |
|
Чтобы отобразить новые значения, добавьте <app-child>
в нижнюю часть шаблона AppComponent
, чтобы представление также отображало подсолнух:
1 2 |
|
В логическом дереве это представлено следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Когда <app-child>
запрашивает FlowerService
, инжектор начинает поиск с <#VIEW>
, принадлежащего <app-child>
(<#VIEW>
включен, потому что он инжектируется из @Component()
) и заканчивается <app-child>
. В этом случае FlowerService
разрешается в массиве providers
с sunflower 🌻 из <app-child>
. Инжектору не нужно искать дальше в дереве инжекторов.
Он останавливается, как только находит FlowerService
, и никогда не видит красный гибискус 🌺.
Использование массива viewProviders
¶
Используйте массив viewProviders
как еще один способ предоставления сервисов в декораторе @Component()
. Использование viewProviders
делает сервисы видимыми в <#VIEW>
.
Шаги те же, что и при использовании массива providers
, за исключением использования массива viewProviders
.
Для получения пошаговых инструкций продолжите этот раздел. Если вы можете настроить его самостоятельно, перейдите к Изменение доступности сервиса.
В примере приложения для демонстрации viewProviders
используется второй сервис, AnimalService
.
Сначала создайте AnimalService
со свойством emoji
кита 🐳:
1 2 3 4 5 6 7 8 |
|
Следуя той же схеме, что и в случае с FlowerService
, внедрите AnimalService
в класс AppComponent
:
1 2 3 4 5 6 |
|
Вы можете оставить весь код, связанный с FlowerService
, на месте, так как он позволит провести сравнение с AnimalService
.
Add a viewProviders
array and inject the AnimalService
in the <app-child>
class, too, but give emoji
a different value. Here, it has a value of dog 🐶.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Добавьте привязки к шаблонам ChildComponent
и AppComponent
. В шаблоне ChildComponent
добавьте следующую привязку:
1 |
|
Кроме того, добавьте то же самое в шаблон AppComponent
:
1 |
|
Теперь вы должны увидеть оба значения в браузере:
1 2 3 4 5 |
|
Логическое дерево для этого примера viewProviders
выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Как и в примере с FlowerService
, AnimalService
предоставляется в декораторе <app-child>
@Component()
. Это означает, что поскольку инжектор сначала ищет в ElementInjector
компонента, он находит значение AnimalService
собаки 🐶.
Ему не нужно продолжать поиск в дереве ElementInjector
, равно как и в ModuleInjector
.
providers
vs. viewProviders
¶
Чтобы увидеть разницу между использованием providers
и viewProviders
, добавьте в пример еще один компонент и назовите его InspectorComponent
. InspectorComponent
будет дочерним компонентом ChildComponent
.
В inspector.component.ts
в конструкторе внедрите FlowerService
и AnimalService
:
1 2 3 4 5 6 |
|
Вам не нужен массив providers
или viewProviders
. Далее, в inspector.component.html
, добавьте ту же разметку, что и в предыдущих компонентах:
1 2 |
|
Не забудьте добавить InspectorComponent
в массив AppModule
declarations
.
1 2 3 4 5 6 7 8 9 10 11 |
|
Далее, убедитесь, что ваш child.component.html
содержит следующее:
1 2 3 4 5 6 7 8 9 10 |
|
Первые две строки с привязками остались от предыдущих шагов. Новые части — это <ng-content>
и <app-inspector>
.
<ng-content>
позволяет вам проектировать контент, а <app-inspector>
внутри шаблона ChildComponent
делает InspectorComponent
дочерним компонентом ChildComponent
.
Далее, добавьте следующее в app.component.html
, чтобы воспользоваться преимуществами проекции содержимого.
1 |
|
Теперь браузер отображает следующее, опуская предыдущие примеры для краткости:
1 2 3 4 5 6 7 8 9 10 |
|
Эти четыре привязки демонстрируют разницу между providers
и viewProviders
. Поскольку собака 🐶 объявлена внутри <#VIEW>
, она не видна проецируемому содержимому. Вместо этого проецируемое содержимое видит кита 🐳.
Однако в следующем разделе, где InspectorComponent
является дочерним компонентом ChildComponent
, InspectorComponent
находится внутри <#VIEW>
, поэтому когда он запрашивает AnimalService
, он видит собаку 🐶.
В логическом дереве AnimalService
будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Проектируемое содержимое <app-inspector>
видит кита 🐳, а не собаку 🐶, потому что собака 🐶 находится внутри <app-child>
<#VIEW>
. Инспектор <app-inspector>
может увидеть собаку 🐶 только если она также находится внутри <#VIEW>
.
Изменение видимости сервиса¶
В этом разделе описывается, как ограничить область видимости начального и конечного ElementInjector
с помощью декораторов видимости @Host()
, @Self()
и @SkipSelf()
.
Видимость предоставляемых лексем¶
Декораторы видимости влияют на то, где в дереве логики начинается и заканчивается поиск маркера инъекции. Для этого размещайте декораторы видимости в точке инъекции, то есть в constructor()
, а не в точке объявления.
Чтобы изменить, где инжектор начинает искать FlowerService
, добавьте @SkipSelf()
в объявление <app-child>
@Inject
для FlowerService
. Это объявление находится в конструкторе <app-child>
, как показано в child.component.ts
:
1 |
|
При использовании @SkipSelf()
инжектор <app-child>
не ищет FlowerService
в себе. Вместо этого инжектор начинает искать FlowerService
в ElementInjector
или <app-root>
, где ничего не находит.
Затем он возвращается к <app-child>
ModuleInjector
и находит значение красного гибискуса 🌺, которое доступно, потому что <app-child>
ModuleInjector
и <app-root>
ModuleInjector
сплющены в один ModuleInjector
.
Таким образом, пользовательский интерфейс отображается следующим образом:
1 |
|
В логическом дереве эта же идея может выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 |
|
Хотя <app-child>
предоставляет подсолнух 🌻, приложение отображает красный гибискус 🌺, потому что @SkipSelf()
заставляет текущий инжектор пропустить себя и обратиться к своему родителю.
Если теперь добавить @Host()
(в дополнение к @SkipSelf()
) к @Inject
службы FlowerService
, результатом будет null
. Это происходит потому, что @Host()
ограничивает верхнюю границу поиска до <#VIEW>
.
Вот идея в логическом дереве:
1 2 3 4 5 6 7 8 9 |
|
Здесь сервисы и их значения одинаковы, но @Host()
не позволяет инжектору искать <#VIEW>
для FlowerService
, поэтому он не находит его и возвращает null
.
В примере приложения используется @Optional()
, поэтому приложение не выбрасывает ошибку, но принципы те же.
@SkipSelf() и viewProviders¶
В настоящее время <app-child>
предоставляет AnimalService
в массиве viewProviders
со значением dog 🐶. Поскольку инжектор должен только посмотреть на ElementInjector
из <app-child>
для AnimalService
, он никогда не увидит кита 🐳.
Как и в примере с FlowerService
, если вы добавите @SkipSelf()
в конструктор для AnimalService
, инжектор не будет смотреть в ElementInjector
текущего <app-child>
для AnimalService
.
1 2 3 4 |
|
Вместо этого инжектор будет начинаться с <app-root>
ElementInjector
. Помните, что класс <app-child>
предоставляет AnimalService
в массиве viewProviders
со значением dog 🐶:
1 2 3 4 5 6 |
|
Логическое дерево выглядит следующим образом с @SkipSelf()
в <app-child>
:
1 2 3 4 5 6 7 8 9 10 11 |
|
С @SkipSelf()
в <app-child>
, инжектор начинает поиск AnimalService
в <app-root>
ElementInjector
и находит кита 🐳.
@Host() и viewProviders¶
Если вы добавите @Host()
в конструктор для AnimalService
, результатом будет собака 🐶 потому что инжектор находит AnimalService
в <app-child>
<#VIEW>
. Вот массив viewProviders
в классе <app-child>
и @Host()
в конструкторе:
1 2 3 4 5 6 7 8 9 10 |
|
@Host()
заставляет инжектор искать, пока он не встретит край <#VIEW>
.
1 2 3 4 5 6 7 8 9 10 |
|
Добавьте массив viewProviders
с третьим животным, ежом 🦔, в метаданные app.component.ts
@Component()
:
1 2 3 4 5 6 |
|
Затем добавьте @SkipSelf()
вместе с @Host()
в конструктор для Animal Service
в child.component.ts
. Вот @Host()
и @SkipSelf()
в конструкторе <app-child>
:
1 2 3 4 5 |
|
Когда @Host()
и SkipSelf()
были применены к FlowerService
, который находится в массиве providers
, результат был null
, потому что @SkipSelf()
начинает поиск в инжекторе <app-child>
, но @Host()
прекращает поиск в <#VIEW>
— где нет FlowerService
В логическом дереве видно, что FlowerService
виден в <app-child>
, а не в его <#VIEW>
.
Однако AnimalService
, который предоставляется в массиве AppComponent
viewProviders
, виден.
Представление логического дерева показывает, почему это так:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
@SkipSelf()
, заставляет инжектор начать поиск AnimalService
в <app-root>
, а не в <app-child>
, откуда пришел запрос, а @Host()
останавливает поиск в <app-root>
<#VIEW>
. Поскольку AnimalService
предоставляется через массив viewProviders
, инжектор находит ежа 🦔 в <#VIEW>
.
Примеры использования ElementInjector¶
Возможность настраивать один или несколько провайдеров на разных уровнях открывает полезные возможности. Чтобы посмотреть на следующие сценарии в работающем приложении, смотрите примеры использования героев.
Сценарий: изоляция сервисов¶
Архитектурные причины могут заставить вас ограничить доступ к службе доменом приложения, к которому она принадлежит. Например, пример руководства включает VillainsListComponent
, который отображает список злодеев.
Он получает этих злодеев из службы VillainsService
.
Если бы вы указали VillainsService
в корневом AppModule
(где вы зарегистрировали HeroesService
), это сделало бы VillainsService
видимым везде в приложении, включая рабочие процессы Hero. Если бы вы позже изменили VillainsService
, вы могли бы сломать что-нибудь в компоненте героя.
Вместо этого вы можете предоставить VillainsService
в метаданных providers
компонента VillainsListComponent
следующим образом:
1 2 3 4 5 |
|
Предоставляя VillainsService
в метаданных VillainsListComponent
и нигде больше, сервис становится доступным только в VillainsListComponent
и его дереве подкомпонентов.
VillainService
является синглтоном по отношению к VillainsListComponent
, потому что именно там он объявлен. Пока VillainsListComponent
не будет уничтожен, он будет тем же самым экземпляром VillainService
, но если есть несколько экземпляров VillainsListComponent
, то каждый экземпляр VillainsListComponent
будет иметь свой собственный экземпляр VillainService
.
Сценарий: несколько сеансов редактирования¶
Многие приложения позволяют пользователям работать над несколькими открытыми задачами одновременно. Например, в приложении для подготовки налогов специалист может работать над несколькими налоговыми декларациями, переключаясь с одной на другую в течение дня.
Чтобы продемонстрировать этот сценарий, представьте внешний компонент HeroListComponent
, который отображает список супергероев.
Чтобы открыть налоговую декларацию героя, специалист по подготовке документов щелкает на имени героя, что открывает компонент для редактирования этой декларации. Налоговая декларация каждого выбранного героя открывается в отдельном компоненте, и несколько деклараций могут быть открыты одновременно.
Каждый компонент налоговой декларации имеет следующие характеристики:
- Является собственным сеансом редактирования налоговой декларации.
- Может изменять налоговую декларацию, не затрагивая декларацию в другом компоненте.
- Имеет возможность сохранить изменения в своей налоговой декларации или отменить их
Предположим, что HeroTaxReturnComponent
имеет логику для управления и восстановления изменений. Это была бы простая задача для героической налоговой декларации. В реальном мире, с богатой моделью данных налоговой декларации, управление изменениями было бы сложным.
Вы можете делегировать это управление вспомогательной службе, как это сделано в данном примере.
Служба HeroTaxReturnService
кэширует одну HeroTaxReturn
, отслеживает изменения в этой декларации, может сохранять или восстанавливать ее. Он также делегирует полномочия синглтону приложения HeroService
, который он получает путем инъекции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Вот HeroTaxReturnComponent
, который использует HeroTaxReturnService
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Данные tax-return-to-edit поступают через свойство @Input()
, которое реализовано с помощью геттеров и сеттеров. Сеттер инициализирует собственный экземпляр компонента HeroTaxReturnService
входящим возвратом.
Геттер всегда возвращает то, что, по словам службы, является текущим состоянием героя.
Компонент также просит службу сохранить и восстановить эту налоговую декларацию.
Это не будет работать, если служба является синглтоном всего приложения. Каждый компонент будет использовать один и тот же экземпляр службы, и каждый компонент будет перезаписывать налоговую декларацию, принадлежащую другому герою.
Чтобы предотвратить это, настройте инжектор компонента HeroTaxReturnComponent
на предоставление сервиса, используя свойство providers
в метаданных компонента.
1 |
|
У HeroTaxReturnComponent
есть свой собственный провайдер HeroTaxReturnService
. Вспомните, что каждый компонент экземпляр имеет свой инжектор.
Предоставление сервиса на уровне компонента гарантирует, что каждый экземпляр компонента получает частный экземпляр сервиса. Это гарантирует, что ни одна налоговая декларация не будет перезаписана.
Остальная часть кода сценария опирается на другие возможности и приемы Angular, о которых вы можете узнать в других разделах документации. Вы можете ознакомиться с ним и загрузить его из примера.
Сценарий: специализированные поставщики услуг¶
Другая причина для повторного предоставления сервиса на другом уровне — это замена более специализированной реализации этого сервиса, расположенной глубже в дереве компонентов.
Например, рассмотрим компонент Car
, который включает информацию о шиномонтаже и зависит от других сервисов для предоставления более подробной информации об автомобиле.
Корневой инжектор, обозначенный как (A), использует общие провайдеры для подробностей о CarService
и EngineService
.
-
Компонент
Car
(A). Компонент (A) отображает данные о шиномонтаже автомобиля и указывает общие службы для предоставления дополнительной информации об автомобиле. -
Дочерний компонент (B). Компонент (B) определяет свои собственные, специализированные провайдеры для
CarService
иEngineService
, которые имеют специальные возможности, подходящие для того, что происходит в компоненте (B). -
Дочерний компонент (C) как дочерний компонент (B). Компонент (C) определяет свой собственный, еще более специализированный провайдер для
CarService
.
За кулисами каждый компонент устанавливает свой собственный инжектор с нулем, одним или несколькими провайдерами, определенными для этого компонента.
Когда вы разрешаете экземпляр Car
в самом глубоком компоненте (C), его инжектор производит:
- Экземпляр
Car
, разрешенный инжектором (C) - Двигатель
Engine
, разрешенный инжектором (B) - Его
Tires
, разрешенный корневым инжектором (A).
Подробнее об инъекции зависимостей¶
Для получения дополнительной информации об инъекции зависимостей Angular смотрите руководства DI Providers и DI in Action.