что такое dependency injection в angular

Dependency injection¶

Все сервисы регистрируются Injector-ом, который является частью механизма DI в Angular. Причем в приложении может быть несколько injector-ов одновременно.

Когда компоненту требуется сервис, то его поиск начинается с самого нижнего injector-а и далее вверх по иерархии, то есть сначала проверяется уровень самого компонента.

Если сервис был найден на одном из нижних уровней, то дальнейший поиск не осуществляется. Если же поиск вообще не даст никаких результатов, то будет сгенерирована ошибка.

Декораторы @SkipSelf() и @Optional()¶

Немного выше уже упоминались два нюанса, связанных с работой Angular Dependency Injection. Первая из них это прекращение поиска, если запрошенный сервис определен на уровне компонента. Но что если вам понадобится, например, обратиться из этого компонента к двум сервисам одновременно: локальному и глобальному?

Angular Providers¶

Самый простой способ зарегистрировать сервис в приложении.

Также в качестве элемента массива свойства Angular providers можно передать объект конфигурации сервиса.

Последние два примера полностью идентичны друг другу.

Конфигурационный объект позволяет переопределить значение для конкретного injection token.

Возможные свойства объекта конфигурации:

В deps перечисляются все зависимости, необходимые для создания factory provider.

Все значения, указываемые в свойстве provide должны существовать в приложении и быть классами, иначе будет неизвестно, что принимать за injection token.

Для того чтобы сделать возможным внедрение в компонент сущность, не являющуюся сервисом Angular, нужно определить для нее вручную injection token.

Источник

Используем DI в Angular по максимуму — концепция частных провайдеров

В Angular очень мощный механизм Dependency Injection. Он позволяет передавать по вашему приложению любые данные, преобразовывать и переопределять их в нужных частях.

Поэтому мы можем делать архитектуру приложений более простой и гибкой: понятный поток данных, минимальная связанность кода, легкость при тестировании или замене зависимостей.

Тем не менее DI в приложениях используется достаточно скромно. Как правило, это внедрение сервисов или передача каких-то глобальных данных сверху вниз по дереву внедрения зависимостей.

В этой статье я хотел бы показать альтернативный вариант работы с полученными из DI данными. Цель: упростить компоненты, директивы и сервисы, которые эти данные используют.

Как обычно используется DI в Angular

Я ежедневно провожу ревью Angular-кода на работе и в опенсорсе. Как правило, в большинстве приложений DI сводится к следующей функциональности:

Angular предоставляет токен DOCUMENT, чтобы можно было получить объект страницы из любого места приложения: ваши компоненты не зависят от глобальных объектов, легко тестировать, ничего не сломается при SSR.

Если вам регулярно нужен доступ до объекта WINDOW, можно написать такой токен:

Когда кто-то запросит токен WINDOW в первый раз из дерева DI, выполнится фабрика токена — он получит объект DOCUMENT у Angular и получит из него ссылку на объект window.

Далее я предлагаю рассмотреть иной подход к таким преобразованиям — когда они выполняются непосредственно в providers компонента или директивы, который и инжектит результат.

Частные провайдеры

В нашей команде мы активно используем DI при работе с Angular и стали замечать, что зачастую нам нужно совершить какие-то преобразования полученных из DI данных перед их использованием. Фактически наш компонент нуждается в одних данных, а мы внедряем в него другие и выполняем логику преобразования внутри него.

Давайте посмотрим сразу на солидном примере. Эрин Коглар в своем докладе The Architecture of Components на большой международной конференции Angular Connect показала такой пример:

Если вам неудобно открывать и смотреть видео, то давайте распишу кейс здесь.

Взять из query-параметров id организации, передать его в метод сервиса, а в ответ получить стрим с информацией об организации. Эту информацию вывести в компоненте.

Рассмотрим три способа добиться желаемого и разберем их.

Как делать не нужно

Иногда я встречаю вот такой стиль работы с данными в компонентах. Пожалуйста, не делайте так:

Чтобы использовать полученные данные в шаблоне:

Этот код будет работать, но у него есть ряд проблем:

В докладе Эрин из видео, что я прикладывал выше, сделано хорошо. С ее вариантом получается примерно так:

Чтобы использовать полученные данные в шаблоне:

Этот код отлично работает и лишен недостатков прошлого подхода: смотрится достаточно аккуратно, в нем нет лишних полей. Если мы захотим расширить компонент аналогичным стримом, мы просто добавим еще один — сделать это проще, потому что для добавления нового стрима нам никак не нужно затрагивать код предыдущего.

Кроме того, поток данных становится более прозрачным: у нас есть только стрим, который создается в момент создания класса компонента. Когда он выдаст данные, будет показана информация в нашем шаблоне.

Сделаем еще круче: частные провайдеры

Давайте присмотримся внимательнее к прошлому решению.

На самом деле компонент не зависит от роутера и даже от OrganizationService. Он зависит от organization$. Но такой сущности в нашем дереве внедрения зависимостей нет, поэтому мы вынуждены выполнять преобразования в компоненте.

Но что если выполнить это преобразование перед тем, как данные попадут в компонент? Давайте напишем Provider специально для компонента, в котором и будут происходить необходимые преобразования.

Для удобства мы выносим провайдеры в отдельный файл рядом с компонентом, получая такую структуру файлов:

В файле organization.providers.ts будут находиться Provider для преобразования данных и токен для их получения в компоненте:

Определим массив провайдеров для компонента. Значение для токена ORGANIZATION_INFO получим из фабрики, в которой сделаем необходимое преобразование данных.

Примечание по работе DI — deps позволяет взять из дерева DI необходимые сущности и передать их как аргументы в фабрику значения токена. В них можно получить любую сущность из DI, в том числе и с использованием DI-декораторов, например:

Объявим providers в компоненте:

И мы готовы к использованию данных в компоненте:

Класс компонента сводится к одной строчке с получением данных.

Шаблон остается прежним:

Что нам дает этот подход?

Заключение

Описанный подход не избавит вас от всех проблем проектирования. Добавлять провайдеры на любую мелочь тоже не стоит: иногда код получается понятнее, если воспользоваться преобразованием в методе или использовать Pipe.

Тем не менее я надеюсь, что частные провайдеры помогут вам упростить компоненты с большим количеством зависимостей или дадут альтернативу при постепенном рефакторинге больших кусков логики.

Источник

Возможности Angular DI, о которых почти ничего не сказано в документации

Angular — это достаточно большой фреймворк. Задокументировать и написать примеры использования для каждого кейса просто невозможно. И механизм внедрения зависимостей не исключение. В этой статье я расскажу о возможностях Angular DI, о которых вы почти ничего не найдете в официальной документации.

Что вы знаете о функции inject?

Документация говорит нам следующее:

Injects a token from the currently active injector. Must be used in the context of a factory function such as one defined for an InjectionToken. Throws an error if not called from such a context.

И дальше мы видим использование функции inject в примере с tree shakable токеном:

Это все, что говорит нам документация.

В фабриках tree shakable провайдеров.

В фабриках провайдеров.

В конструкторах сервисов.

В конструкторах модулей.

Так как фабричная функция InjectionToken не может иметь аргументы, inject — единственный способ получить данные из инжектора. Но зачем нам эта функция в сервисах, если можно просто указать необходимые зависимости прямо в параметрах конструктора?

Рассмотрим небольшой пример.

Ручная установка контекста для функции inject

Если внимательно посмотреть, то работа функции inject становится совершенно очевидной:

вызов функции setCurrentInjector присваивает в приватную переменную _currentInjector переданный инжектор, возвращая предыдущий;

функция inject достает из _currentInjector значение по переданному токену.

Это настолько просто, что мы совершенно спокойно можем заставить работать функцию inject даже в компонентах и директивах:

Правда, выглядит это совершенно неюзабельно. Да и использование приватных функций Angular тоже не входит в лучшие практики. Поэтому не советую заниматься подобными вещами.

Injection flags

InjectFlags — это аналог модификаторов Optional, Self, SkipSelf и Host. Используются в функциях inject и Injector.get. Документация и здесь не подвела — ее почти нет:

Читайте также:  не могу удалить историю в инстаграме почему

Человек знающий сразу увидит здесь битовые маски. Этот же enum можно представить немного в другом виде:

Использование одного флага

Вот так можно получить поток событий роутера, не беспокоясь о том, что модуль роутинга не подключен:

Выглядит просто. А на деле — еще и безопасно, без неожиданных падений и лишних событий.

Комбинация флагов

Комбинацию флагов можно использовать при проверке, что модуль импортировался один раз. А комбинируются они при помощи побитового ИЛИ:

Значение нужного бита получается с помощью побитового И:

Tree shakable сервисы и *SansProviders

Tree shakable сервисы — это специальный способ сказать компилятору, что сервис не нужно включать в сборку, если он нигде не используется. При этом сервис не указывается в модуле, скорее наоборот — модуль указывается в сервисе:

Как мы видим из примера, в проперти providedIn указан модуль. Это работает точно так же, как и следующий пример:

Самый полезный, по моему мнению, способ использования фабрики в tree shakable сервисах выглядит так:

Плюсы такого определения сервиса:

Исключено случайное использование сервиса. Для работы с ним необходимо импортировать модуль сервиса.

Гарантировано создание одного экземпляра сервиса.

С остальными способами определения не все так очевидно. Я могу предположить, что это поможет при использовании внешних библиотек, в которых еще и отсутствует типизация.

Для остальных типов провайдеров я предлагаю придумать use-кейсы вам самим. Буду рад, если вы поделитесь ими в комментариях.

Взаимодействие компонент

В документации перечислены все существующие способы взаимодействия компонент и директив между собой:

Input binding with a setter, ngOnChanges.

Child events (outputs).

Но ни слова не сказано о том, что дочерняя директива/компонент совершенно спокойно может получить инстанс родителя через DI.

UPD: нашлась все таки ссылочка на документацию, где описывается этот способ.

На примере видно, что дочерняя директива получает инстанс компонента родителя, упрощая их взаимодействие. Обрабатывая клик по хостовому элементу, директива может напрямую поменять значение родительского компонента. Использование любого другого способа было бы как минимум более многословным.

Но почему это возможно? Ответ может быть очень большим, и он явно выходит за рамки этой статьи. Когда-нибудь я напишу об этом более подробно. А пока предлагаю вам прочитать об иерархии инжекторов и ElementInjector (именно в таком порядке).

Вместо вывода

Как я уже говорил, Angular — очень большой фреймворк. И я уверен, что в его исходниках можно найти намного больше интересных возможностей.

Своими находками я всегда делюсь в своем Твиттере. Например, советы по Angular вы можете найти по хэштегу #AngularTip. А перевод самых интересных твитов — по хэштегу #AngularTipRus! Буду рад, если вы поделитесь своими наблюдениями и советами со мной и сообществом. Спасибо за внимание!

Список хороших статей об Angular DI

Я вспомнил еще 2 статьи об Angular DI от @MarsiBarsi на русском языке. Пишите где угодно, предлагайте и давайте его пополнять!

Источник

Angular 6+ полное руководство по внедрению зависимостей. providedIn vs providers:[]

В Angular 6 появился новый улучшенный синтаксис для внедрения зависимостей сервисов в приложение (provideIn). Несмотря на то, что уже вышел Angular 7, эта тема до сих пор остается актуальной. Существует много путаницы в комментариях GitHub, Slack и Stack Overflow, так что давайте подробно разберем эту тему.

В данной статье мы рассмотрим:

Внедрение зависимостей (dependency Injection)

Внедрение зависимостей (DI) — это способ создания объектов, которые зависят от других объектов. Система внедрения зависимостей предоставляет зависимые объекты, когда создает экземпляр класса.

Формальные объяснения хороши, но давайте разберем более подробно, что такое внедрение зависимостей.

Все компоненты и сервисы являются классами. Каждый класс имеет специальный метод constructor, при вызове которого создается объект-экземпляр данного класса, использующийся в приложении.

Допустим в одном из наших сервисов имеется следующий код:

Если создавать его, не используя механизм внедрения зависимостей, то необходимо добавить HttpClient вручную. Тогда код будет выглядеть следующим образом:

Но откуда в таком случае взять httpClient? Его тоже необходимо создать:

Но откуда теперь взять httpHandler? И так далее, пока не будут созданы экземпляры всех необходимых классов. Как мы видим, ручное создание может быть сложным и в процессе могут возникать ошибки.

Механизм внедрения зависимостей Angular делает все это автоматически. Все, что нам нужно сделать, это указать зависимости в конструкторе компонентов, и они будут добавлены без каких-либо усилий с нашей стороны.

Старый способ внедрения зависимостей в Angular (providers: [])

Для запуска приложения Angular должен знать о каждом отдельном объекте, который мы хотим внедрить в компоненты и сервисы. До релиза Angular 6 единственным способом сделать это было указание сервисов в свойстве providers: [] декораторов @NgModule, @Сomponent и @Directive.

Разберем три основных случая использования providers: []:

Модули, загружаемые с приложением (Eager)

В данном случае сервис регистрируется в глобальной области видимости как синглтон. Он будет синглтоном даже если включен в providers[] нескольких модулей. Создается единственный экземпляр класса сервиса, который будет зарегистрирован на уровне корня приложения.

Модули с отложенной загрузкой (Lazy)

Экземпляр сервиса, подключенного к lazy модулю, будет создан во время его инициализации. Добавление такого сервиса в компонент eager модуля приведет к ошибке: No provider for MyService! error.

Внедрение в @Сomponent и @Directive

При внедрении в компонент или директиву создается отдельный экземпляр сервиса, который будет доступен в данном компоненте и всех дочерних. В этой ситуации сервис не будет синглтоном, его экземпляр будет создаваться каждый раз при использовании компонента и удаляться вместе с удалением компонента из DOM.

Новый способ внедрения зависимостей в Angular (providedIn: ‘root’ | SomeModule)

В Angular 6 мы получили новый инструмент “Tree-shakable providers” для внедрения зависимостей в приложение, который можно использовать с помощью свойства providedIn декоратора @Injectable.

Можно представить providedIn как внедрение зависимостей в обратном направлении: раньше в модуле описывались сервисы, в которые он будет подключен, теперь в сервисе определяется модуль, к которому его подключать.

Сервис может быть внедрен в корень приложения(providedIn: ‘root’) или в любой модуль (providedIn: SomeModule). providedIn: ‘root’ является сокращением для внедрения в AppModule.

Разберем основные сценария использования нового синтаксиса:

Внедрение в корневой модуль приложения (providedIn: ‘root’)

Это самый распространенный вариант внедрения зависимостей. В данном случае сервис будет добавлен в бандл приложение только если он реально используется, т.е. внедрен в компонент или другой сервис.

При использовании нового подхода не будет особой разницы в монолитном SPA приложении, где используются все написанные сервисы, однако providedIn: ‘root’ будет полезен при написании библиотек.

Раньше все сервисы библиотеки необходимо было добавить в providers:[] её модуля. После импорта библиотеки в приложение в бандл добавлялись все сервисы, даже если использовался только один. В случае с providedIn: ‘root’ нет необходимости подключать модуль библиотеки. Достаточно просто внедрить сервис в нужный компонент.

Модуль с отложенной загрузкой (lazy) и providedIn: ‘root’

Что произойдет, если внедрить сервис с providedIn: ‘root’ в lazy модуль?

Технически ‘root’ обозначает AppModule, но Angular достаточно умен, чтоб добавить сервис в бандл lazy модуля, если он внедрен только в его компоненты и сервисы. Но есть одна проблема (хотя некоторые люди утверждают, что это фича). Если позже внедрить сервис, используемый только в lazy модуле, в основной модуль, то сервис будет перенесен в основной бандл. В больших приложениях с множеством модулей и сервисов это может привести к проблемам с отслеживанием зависимостей и непредсказуемому поведению.

Будьте внимательны! Внедрение одного сервиса во множестве модулей может привести к скрытым зависимостям, которые сложно понять и невозможно распутать.

К счастью есть способы предотвратить это, и мы рассмотрим их ниже.

Внедрение зависимости в немедленно загружаемый модуль (eager)

Как правило, этот кейс не имеет смысла и вместо него мы можем использовать providedIn: ‘root’. Подключение сервиса в EagerModule может использоваться для инкапсуляции и предотвратит внедрение без подключения модуля, но в большинстве случаев такой необходимости нет.

Читайте также:  доверенность на получение вида на жительство в россии образец

Если действительно понадобится ограничить область видимости сервиса, проще воспользоваться старым способом providers:[], так как он точно не приведет к циклическим зависимостям.

По возможности старайтесь использовать providedIn: ‘root’ во всех eager модулях.

Примечание. Преимущество модулей с отложенной загрузкой(lazy)

Одной из основных фич Angular является возможность легко разбивать приложение на фрагменты, что дает следующие преимущества:

Еще одним преимуществом изолированности lazy модуля является то, что ошибка, допущенная в нем, не повлияет на остальную часть приложения. Теперь можно спать спокойно даже в день релиза.

Внедрение в модуль с отложенной загрузкой(providedIn: LazyModule)

Внедрение зависимости в определенный модуль не дает использовать сервис в остальных частях приложения. Это позволяет сохранить структуру зависимостей, что особо полезно для больших приложений, в которых беспорядочное внедрение зависимостей может привести к путанице.

Интересный факт: Если lazy сервис внедрить в основную часть приложения, то сборка (даже AOT) пройдет без ошибок, но приложение упадет с ошибкой «No provider for LazyService».

Проблема с циклической зависимостью

Воспроизвести ошибку можно следующим образом:

Решить эту проблему можно, создав подмодуль LazyServiceModule, который будет подключен в LazyModule. К подмодулю подключить сервисы.

В данном случае придется создать дополнительный модуль, но это не потребует много усилий и даст следующие плюсы:

Внедрение сервиса в компонент (providedIn: SomeComponent)

Существует ли возможность внедрить сервис в @Сomponent или @Directive с использованием нового синтаксиса?

На данный момент нет!

Для создания экземпляра сервиса на каждый компонент все так же необходимо использовать providers: [] в декораторах @Сomponent или @Directive.

Рекомендации по использованию нового синтаксиса в приложениях

Библиотеки

providedIn: ‘root’ хорошо подходит для создания библиотек. Это действительно удобный способ подключить в основное приложение только непосредственно используемую часть функционала и уменьшить размер конечной сборки.

Одним из практических примеров является библиотека ngx-model, которая была переписана с использованием нового синтаксиса и теперь называется @angular-extensions/model. В новой реализации нет необходимости подключать NgxModelModule в приложение, достаточно просто внедрить ModelFactory в нужный компонент. Подробности реализации можно посмотреть тут.

Модули с отложенной загрузкой(lazy)

Используйте для сервисов отдельный модуль providedIn: LazyServicesModule и подключайте его в LazyModule. Такой подход инкапсулирует сервисы и не даст подключить их в другие модули. Это обозначит границы и поможет создать масштабируемую архитектуру.

По моему опыту случайное внедрение в основной или дополнительный модуль (с использованием providedIn: ‘root’) может привести к путанице и является не лучшим решением!

providedIn: ‘root’ тоже будет работать корректно, но при использовании providedIn: LazyServideModule мы получим ошибку «missing provider» при внедрении в другие модули и сможем исправить архитектуру. Перенести сервис в более подходящее место в основной части приложения.

В случаях, когда необходимо конфигурировать модуль. Например, подключать сервис только в SomeModule.forRoot(someConfig).

С другой стороны, в такой ситуации можно использовать providedIn: ‘root’. Это даст гарантию того, что сервис будет добавлен в приложение только один раз.

Источник

Внедрение зависимостей в Angular 2

Добрый вечер, уважаемые дамы и господа!

При всей неослабевающей популярности фреймворка AngularJS мы все-таки не успели отметиться с книгой по его первой версии, а теперь решили не дожидаться второй и поинтересоваться: насколько вам импонирует вот эта работа, охватывающая кроме AngularJS и более широкий контекст JavaScript-разработки?

Под катом вы найдете перевод регулярно обновляемой статьи Паскаля Прехта (версия от 12 октября 2015 года), рассказывающей о таких высоких материях, как внедрение зависимостей в AngularJs и, что самое интересное, тех доработках, которые ждут этот механизм в Angular 2.

Внедрение зависимостей всегда было одной из наиболее заметных и козырных особенностей Angular. Итак, этот фреймворк позволяет внедрять зависимости в различные компоненты в разных точках приложения; при этом не требуется знать, как эти зависимости создаются, либо в каких зависимостях они в свою очередь нуждаются. Однако, оказывается, что современный механизм внедрения зависимостей (в Angular 1) имеет некоторые проблемы, которые предстоит решить в Angular 2, чтобы построить фреймворк нового поколения. В этой статье мы поговорим о новой системе внедрения зависимостей — для будущих поколений.

Прежде чем приступить к изучению нового материала, давайте разберемся, что же такое «внедрение зависимостей» (DI) и какие проблемы с DI возникают в Angular 1.

Войта Джина сделал отличный доклад о внедрении зависимостей на конференции ng-conf 2014. В своем выступлении он изложил историю создания новой системы внедрения зависимостей, которая будет разрабатываться для Angular 2, описал идеи, на которых она базируется. Кроме того, он четко описал, что DI можно рассматривать в двух ипостасях: как паттерн проектирования и как фреймворк. В первом случае объясняются шаблоны применения DI, а во втором случае речь идет о системе поддержки и сборки зависимостей. Эту статью я собираюсь построить таким же образом, поскольку так будет проще объяснить всю эту концепцию

Для начала рассмотрим следующий код и разберемся, какие проблемы в нем возникают.

Ничего особенного. Имеем класс Car
с конструктором, в котором задаем все необходимое для создания объекта car («автомобиль»), как только он нам понадобится. В чем проблема с этим кодом? Как видите, конструктор не только присваивает необходимые зависимости внутренним свойствам, но и знает, как создается их объект. Например, объект engine («двигатель») создается при помощи конструктора Engine
, Tires
(шины) по-видимому, представляет собой интерфейс-одиночку, а doors
(дверцы) запрашиваются через глобальный объект, действующий в качестве локатора сервисов.

Получается код, который сложно поддерживать и еще сложнее тестировать. Только вообразите себе, что хотели бы протестировать этот класс. Как вы заменили бы в этом коде Engine
зависимостью MockEngine
? При написании тестов мы хотим проверить различные сценарии, в которых может использоваться наш код, поскольку для каждого сценария требуется собственная конфигурация. Если нам нужен тестируемый код, то подразумевается, что это будет код многократного использования. Что приводит нас к тезису, что тестируемый код = переиспользуемый код и наоборот.

Итак, как можно было бы улучшить этот код и сделать его более удобным для тестирования? Это очень просто, и вы уже, вероятно, понимаете, о чем речь. Меняем код так:

Мы просто вынесли создание зависимости из конструктора и расширили функцию конструктора – теперь она рассчитывает на все необходимые зависимости. В коде не осталось конкретных реализаций, мы буквально переместили ответственность за создание этих зависимостей на уровень выше. Если бы мы сейчас хотели создать объект car, то должны были бы всего лишь передать все необходимые зависимости конструктору:

Круто? Теперь зависимости отделены от нашего класса, что позволяет нам сообщать мок-зависимости при написании тестов:

Представляете, это и есть внедрение зависимости. Точнее, именно этот паттерн еще называется «внедрение конструктора«. Есть еще два паттерна: внедрение через метод класса (setter injection) и внедрение через интерфейс (interface injection), но мы не будем рассматривать их в этой статье.

Отлично, вот мы и используем DI, но где начинает задействоваться система DI? Как уже упоминалось ранее, мы буквально сдвинули ответственность за создание зависимостей на уровень выше. Именно в этом заключается наша новая проблема. Кто организует сборку всех этих зависимостей для нас? Мы сами.

Сейчас нам нужна поддержка функции main. Делать это вручную весьма обременительно, особенно по мере роста приложения. Может быть, лучше поступить примерно так?

Внедрение зависимостей как фреймворк

Здесь мы начинаем использовать внедрение зависимостей как фреймворк. Как известно, в Angular 1 имеется собственная система DI, позволяющая аннотировать сервисы и другие компоненты; также с ее помощью инжектор может узнать, какие зависимости необходимо инстанцировать. Например, в следующем коде показано, как можно аннотировать наш класс Car
в Angular 1:

Затем мы регистрируем наш Car
как сервис и всякий раз, запрашивая его, получаем от него экземпляр-одиночку, совершенно не заморачиваясь о создании необходимых зависимостей для «автомобиля».

Все классно, но выясняется, что у имеющегося механизма DI все-таки есть некоторые проблемы:

Эти проблемы необходимо решить, чтобы перевести внедрение зависимостей в Angular на новый уровень.

Читайте также:  Эрхпг поджелудочной железы что такое

Внедрение зависимостей в Angular 2

Прежде чем перейти к рассмотрению кода, нужно понять концепцию, лежащую в основе DI в Angular 2. Следующий рисунок иллюстрирует обязательные компоненты новой системы DI.

Внедрение зависимостей в Angular 2 принципиально состоит из трех элементов:

Итак, получив представление об этой концепции, рассмотрим, как она реализуется в коде. Продолжим работать с нашим классом Car
и его зависимостями. Вот как можно использовать внедрение зависимостей в Angular 2, чтобы получить экземпляр Car
:

Мы импортируем из Angular 2 Injector
, предоставляющий некоторые статические API для создания инжекторов. Метод resolveAndCreate()
– это, по сути, фабричная функция, создающая инжектор и принимающая список провайдеров. Вскоре мы обсудим, как предполагается использовать эти классы в качестве провайдеров, но пока сосредоточимся на injector.get()
. Видите, как мы запрашиваем экземпляр Car
в последней строке? Как наш инжектор узнает, какие зависимости необходимо создать, чтобы инстанцировать «car»? Что ж, рассмотрим класс Car

Мы импортируем из фреймворка сущность Inject и применяем ее в качестве декоратора к параметрам нашего конструктора.

Декоратор Inject
прикрепляет метаданные к нашему классу Car
, который затем потребляется нашей системой DI. В принципе, вот что мы здесь делаем: сообщаем DI, что первый параметр конструктора должен быть экземпляром типа Engine
, второй — типа Tires
и третий — типа Doors
. Мы можем переписать этот код в духе TypeScript, чтобы он выглядел более естественно:

Отлично, наш класс объявляет собственные зависимости, а DI может считывать эту информацию и инстанцировать все, что необходимо для создания объекта Car
. Но каким образом инжектор узнает, как создать такой объект? Здесь-то в игру и вступают провайдеры. Помните метод resolveAndCreate()
, в котором мы передали список классов?

Опять же, может возникнуть вопрос, как этот список классов будет выступать в качестве списка провайдеров.
Оказывается, что это просто сокращенный синтаксис. Если преобразовать его в более длинный и пространный вариант, то ситуация несколько прояснится:

У нас есть функция provide()
, отображающая маркер на конфигурационный объект. Этот маркер может быть типом либо строкой. Если сейчас прочитать эти провайдеры, то становится гораздо понятнее, что происходит. Мы предоставляем экземпляр типа Car
через класс Car
, тип Engine
через класс Engine
и т.д. Это и есть механизм-рецепт, о котором мы говорили выше. Итак, при помощи провайдеров мы не просто сообщаем инжектору, какие зависимости используются во всем приложении, но также описываем, как будут создаваться объекты этих зависимостей.

Теперь возникает следующий вопрос: когда желательно использовать более длинный синтаксис вместо более краткого? Зачем писать provide(Foo, )
, если можно обойтись одним Foo, верно? Да, верно. Вот почему мы сразу начали с сокращенного синтаксиса. Однако более длинный синтаксис открывает перед нами большие, очень большие возможности. Взгляните на следующий фрагмент кода.

Верно. Мы можем отображать маркер практически на что угодно. Здесь мы отображаем маркер Engine
на класс OtherEngine
. Таким образом, если мы теперь запросим объект типа Engine
, то получим экземпляр класса OtherEngine
.
Это невероятно мощный механизм, позволяющий не только избегать конфликтов имен, но и создавать тип как интерфейс, привязывая его к конкретной реализации. Кроме того, мы можем выгружать в нужном месте ту или иную зависимость, заменяя ее маркером и не трогая остальной код.

Внедрение зависимостей в Angular 2 также привносит еще пару рецептов провайдеров, о которых мы поговорим в следующем разделе.

Другие конфигурации провайдеров

Иногда мы хотим получить не экземпляр класса, а просто отдельное значение или фабричную функцию, в которой может потребоваться дополнительная конфигурация. Поэтому механизм провайдеров при работе с DI в Angular 2 предусматривает несколько рецептов. Давайте быстро пробежимся по ним.

Можно предоставить простое значение при помощи

Это удобно, когда мы хотим предоставлять простые конфигурационные значения.

Можно отображать маркер-псевдоним на другой маркер, вот так:

Да, наши любимые фабрики

Разумеется, фабрика может принимать собственные зависимости. Чтобы передать зависимости фабрике, достаточно просто добавить к этой фабрике список маркеров:

Декоратор @Optional
позволяет объявлять зависимости как необязательные. Это бывает удобно, к примеру, в тех случаях, когда наше приложение рассчитывает на стороннюю библиотеку, а если эта библиотека оказывается недоступна – нужен механизм отката.

Как видите, DI в Angular 2 решает практически все проблемы, существовавшие с DI в Angular. Но один вопрос мы еще не обсудили. Создаются ли по-прежнему одиночки при новом DI? Да.

Одноразовые зависимости и дочерние инжекторы

Если нам понадобится одноразовая зависимость (transient dependency) – такая, при запрашивании которой нам всегда требуется ее новый экземпляр, есть два варианта:

Фабрики могут возвращать экземпляры классов. Это будут не одиночки.

Можно создать дочерний инжектор при помощи Injector.resolveAndCreateChild()
. Дочерний инжектор привносит собственные привязки, и этот экземпляр будет отличен от родительского инжектора.

Дочерние инжекторы интересны не только этим. Оказывается, дочерний инжектор может искать привязку маркера на родительском инжекторе, если на дочернем инжекторе никакой привязки для конкретного маркера не зарегистрировано. На следующей схеме показано, что происходит:

На рисунке показано три инжектора, два из которых – дочерние. Каждый инжектор получает собственную конфигурацию провайдеров. Теперь, если мы запросим у второго дочернего инжектора экземпляр типа Car, то этот дочерний инжектор создаст объект «car». Однако «engine» (двигатель) будет создаваться первым дочерним инжектором, а «tires» (шины) и «doors» (дверцы) – самым вышестоящим из внешних родительских инжекторов. Получается нечто вроде цепочки прототипов.

Можно сконфигурировать даже видимость зависимостей, а также указать уровень, вплоть до которого дочерний инжектор должен искать информацию. Однако это тема для другой статьи.

Как же все это будет работать в Angular 2?

Теперь, когда мы рассмотрели внедрение зависимостей в Angular 2, давайте обсудим, как этот механизм работает в рамках фреймворка. Должны ли мы создавать инжекторы вручную, когда собираем компоненты Angular 2? К счастью, команда Angular не жалела времени и сил, и создала красивый API, скрывающий всю машинерию инжектора при сборке компонентов в Angular 2.

Рассмотрим следующий простой компонент Angular 2.

Ничего особенного. Допустим, мы хотим расширить этот компонент при помощи NameService
, используемого в конструкторе компонента. Такой сервис может выглядеть следующим образом:

Опять же, ничего особенного. Просто создаем класс. Затем, чтобы открыть к нему в нашем приложении доступ как к внедряемому объекту, мы должны сообщить инжектору нашего приложения некоторые данные о конфигурации провайдера. Но как это сделать? Мы еще даже не создали инжектор.

Метод bootstrap()
занимается созданием корневого инжектора для нашего приложения при его начальной загрузке. Он принимает список провайдеров в качестве второго аргумента, и этот список будет передаваться непосредственно инжектору прямо на этапе его создания. Иными словами, вот что нужно здесь сделать:

Вот и все. Теперь, переходя к внедрению как к таковому, мы применяем изученные выше декораторы @Inject
.

Либо, если остановимся на TypeScript, можем просто добавить аннотации типов к нашему конструктору:

Шикарно! Вся нутрянка Angular чудесным образом куда-то исчезла! Но остается еще один вопрос: что делать, если для конкретного компонента нам понадобится иная конфигурация привязок?

Допустим, у нас есть сервис NameService
, который можно внедрять в пределах всего приложения для типа NameService
, но ровно одному компоненту требуется иной сервис? Здесь-то нам и пригодится свойство providers
аннотации @Component
. Оно позволяет нам добавлять провайдеры к конкретному компоненту (а также его дочерним компонентам).

Поясню: providers
не конфигурирует экземпляры, которые будут внедряться. Оно конфигурирует дочерний инжектор, который будет создаваться для этого компонента. Как было указано выше, мы также можем конфигурировать видимость наших привязок, точнее — указывать, какой компонент что может внедрять. Например, свойство viewProviders позволяет открывать доступ к зависимостям только для представления компонента, но не для его потомков.

Новая система внедрения зависимостей в Angular решает все проблемы, существовавшие с DI в Angular 1. Больше никаких конфликтов имен. Это отдельный компонент фреймворка, может использоваться как самостоятельная система, даже без Angular 2.

Источник

Академический образовательный портал