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

Знаете ли вы свои зависимости?
Иногда нелегко понять, сколько зависимостей имеет ваш код.
Например, взгляните на этот псевдокласс и посчитайте, сколько зависимостей он имеет:
import { API_URL } from '../../../env/api-url';import { Logger } from '../../services/logger'; class PseudoClass { request() { fetch(API_URL).then(...); } onError(error) { const logger = new Logger(); logger.log(document.location, error); }}
API_URL импортированные данные из другого файла тоже можно считать зависимостью вашего класса (зависимость от расположения файла).
new Logger() также импортированные данные из другого файла и пересоздания множества экземпляров класса, когда нам достаточно лишь одного.
document также браузерное API и завязка на глобальную переменную.
Ну и что же не так?
Например, такой класс тяжело протестировать, так как он зависит от импортированных данных из других файлов и конкретных сущностей в них.
Другая ситуация: document и fetch будут без проблем работать в вашем браузере. Но если однажды вам потребуется перенести приложение в Server Side Rendering, то в nodejs окружении необходимых глобальных переменных может не быть.
Так и что же за DI и зачем он нужен?
Механизм внедрения зависимостей управляет зависимостями внутри приложения. В принципе, для нас, как для Angular-разработчиков, эта система довольно простая. Есть две основные операции: положить что-то в дерево зависимостей или получить что-то из него.
Механизм внедрения зависимостей управляет зависимостями внутри приложения. В принципе, для нас, как для Angular-разработчиков, эта система довольно простая. Есть две основные операции: положить что-то в дерево зависимостей или получить что-то из него.
Если хотите рассмотреть DI с более теоретической стороны, почитайте о принципе инверсии управления. Также можете посмотреть интересные видеоматериалы по теме: серия видео про IoC и DI у Ильи Климова на русском или небольшое видео про IoC на английском.
Вся магия возникает от порядка, в котором мы поставляем и берем зависимости.
Схема работы областей видимости в DI:

Что мы можем положить в DI?
Первая из операций с DI что-то положить в него. Собственно, для этого Angular позволяет нам прописывать providers-массив в декораторах наших модулей, компонентов или директив. Давайте посмотрим, из чего этот массив может состоять.
Providing класса
Обычно это знает каждый разработчик на Angular. Это тот случай, когда вы добавляете в приложение сервис.
Angular создает экземпляр класса, когда вы запрашиваете его в первый раз. А с Angular 6 мы можем и вовсе не прописывать классы в массив providers, а указать самому классу, в какое место в DI ему встать с providedIn:
providers: [ { provide: SomeService, useClass: SomeService }, // Angular позволяет сократить эту запись как самый частый кейс: SomeService]
Providing значения
Также через DI можно поставлять константные значения. Это может быть как простая строка с URL вашего API, так и сложный Observable с данными.
Providing значения обычно реализуется в связке с InjectionToken. Этот объект ключ для DI-механизма. Сначала вы говорите: Я хочу получить вот эти данные по такому ключу. А позже приходите к DI и спрашиваете: Есть ли что-то по этому ключу?
Ну и частый кейс проброс глобальных данных из корня приложения.
Лучше посмотреть это сразу в действии, поэтому давайте взглянем на stackblitz с примером:
Итак, в примере мы получили зависимость из DI вместо того, чтобы импортировать ее как константу из другого файла напрямую. И почему нам от этого лучше?
- Мы можем переопределить значение токена на любом уровне дерева DI, никак не меняя компоненты, которые его используют.
- Мы можем мокировать значение токена подходящими данными при тестировании.
- Компонент полностью изолирован и всегда будет работать одинаково, независимо от контекста.
Providing фабрики
На мой взгляд, это самый мощный инструмент в механизме внедрения зависимостей Angular.
Вы можете создать токен, в котором будет результат комбинации и преобразования значений других токенов.
Вот еще один stackbitz с подробным примером создания фабрики со стримом.
Можно найти много кейсов, когда providing фабрики экономит время или делает код более читабельным. Иногда мы внедряем зависимости в компоненты только ради того, чтобы совместить их или преобразовать в совершенно иной формат. В предыдущей статье я рассматривал этот вопрос подробнее и показывал альтернативный подход к решению таких ситуаций.
Providing существующего экземпляра
Не самый частый случай, но этот вариант бывает очень полезным инструментом.
Вы можете положить в токен сущность, которая уже была создана. Хорошо работает в связке с forwardRef.
Посмотрите еще один пример со stackblitz с директивой, которая имплементирует интерфейс и подменяет собой другой токен через useExisting. В этом примере мы хотим переопределить значение токена только в области видимости DI для дочерних компонентов элемента, на котором висит директива. Причем директива может быть любой главное, что она реализует необходимый интерфейс.
Хитрости с DI-декораторами
DI-декораторы позволяют сделать запросы к DI более гибкими.
Если вы не знаете все четыре декоратора, советую почитать вот эту статью на Медиуме. Статья на английском, но там очень классные и понятные визуализации по теме.
Не многие также знают, что DI-декораторы можно использовать в массиве deps, который готовит аргументы для фабрики в providers.
providers: [ { provide: SOME_TOKEN, /** * Чтобы фабрика получила декорированное значение, используйте такой * [new Decorator(), new Decorator(),..., TOKEN] * синтаксис. * * В этом случае вы получите null, если не будет значения для * OPTIONAL_TOKEN */ deps: [[new Optional(), OPTIONAL_TOKEN]], useFactory: someTokenFactory } ]
Фабрика токена
Конструктор InjectionToken принимает два аргумента.
Второй аргумент объект с конфигурацией токена.
Фабрика токена это функция, которая вызывается в момент, когда кто-то запрашивает этот токен в первый раз. В ней можно посчитать некое стандартное значение для токена или даже обращаться к другим DI-сущностям через функцию inject.
Посмотрите пример реализации функционала стрима нажатия кнопок, но уже на фабрике токена.
Заключение
DI в Angular удивительная тема: на первый взгляд, в нем не так много различных рычагов и инструментов для изучения, но можно писать и говорить часами о тех возможностях и способах применения, которые они нам дают.
Надеюсь, этой статьей мне удалось дать вам фундамент, на основе которого вы сможете придумать собственные решения для упрощения работы с данными в ваших приложениях и библиотеках.