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