Внедрение зависимостей в Angular простыми словами
- вторник, 1 августа 2023 г. в 00:00:26

Всем привет👋 Меня зовут Данила, фронтенд разработчик в ПСБ. Angular я начал изучать не так давно, поэтому часто встречаются сложные темы, которые непонятны и их нужно разбирать. Одной из таких тем и стало внедрение зависимостей (Dependency Injection). Что ж, давайте разбираться :)
Внедрение зависимостей, или DI, — это шаблон проектирования и механизм для переиспользования кода в разных частях приложения.
Простой пример - чай с сахаром. У нас есть чай, но без сахара он не такой вкусный, как с ним. Чтобы получить вкусный напиток, мы берём чайную ложку (инжектор), набираем ей сахар (зависимость) из сахарницы (провайдера) и кладём в кружку.
Чаще всего в Angular зависимостями являются сервисы, но могут быть и другие значения.
Сервис в Angular создаётся так:
@Injectable({ // Декоратор, который указывает DI, что этот класс можно инжектить
providedIn: 'root'
})
class Service {}Свойство provideIn указывает, куда именно инжектить. Его доступные значения:
root - в корень по запросу
platform - позволяет использовать сервис в двух или более корневых компонентов (микросервисы, виджеты)
any - в каждую область внедрения (то есть в обычных модулях, экземпляр будет общий, а в ленивых, у каждого свой. P.S. в 17 версии удалят)
Представим, что у нас есть 10 сервисов, но на странице мы используем только 2. Тогда, благодаря внедрению зависимостей, мы не будем вызывать 8 неиспользуемых сервисов. Это позволит уменьшить размер пакета и потребление ресурсов.
Инжекторы ищут зависимости, а провайдеры их предоставляют.
Самый простой способ указать провайдера:
providers: [Service] // где, Service - зависимостьЭту запись можно интерпретировать как:
providers: [{ provide: Service, useClass: Service }]
// где, provide (token) - ключ для поиска зависимости,
// а useClass - создаёт экземпляр класса при внедренииЧто ж, с DI разобрались. Что дальше? Внедрение зависимостей использует Injector и его иерархии. Рассмотрим его работу:
В Angular есть 2 типа чайных ложек инжекторов: ModuleInjector (создаётся явно и используется в модулях и в @Injectable()) и ElementInjector (создаётся неявно в каждом элементе DOM с пустым значением, настраивается в providers компонентов и директив).
При создании ElementInjector в конструкторе директивы/компонента, он появляется в лексическом окружении и при дальнейшем запросе Angular будет искать его от вашего текущего модуля (например, страницы), поднимаясь вверх по иерархии, пока не дойдёт до самого высокого - @NullInjector(), который всегда выбрасывает ошибку (если только вы не используете модификатор @Optional()).
При создании компонента/директивы мы получаем зависимости в конструкторе класса. Благодаря модификаторам, можно определить видимость или способ создания экземпляров:
constructor(@Optional() service: Service) {}Существует 4 типа:
@Optional() - необязательная зависимость (используется в случае, когда неизвестно, будет ли зависимость или нет)
@Self() - ищет зависимость локально в модуле
@SkipSelf() - ищет зависимость, начиная с родительского элемента (скоупа)
@Host() - указывает, что если механизм DI дошёл до хоста, то нужно остановить поиск зависимости
При необходимости, их можно миксовать. Но не все, нельзя смешивать @Host() и @Self(), а также @SkipSelf() и @Self().
Существует 4 метода:
useClass - как и писал ранее, создаёт экземпляр класса
useExisting - в отличие от предшественника, использует уже созданный экземпляр (полезно для синглтонов, когда один экземпляр используется во всём приложении)
useFactory - использует функцию для внедрения зависимости (удобно для переопределения логики, например, передать в аргументы функции сервис, получить из сервиса информацию и использовать её в конструкторе)
useValue - позволяет использовать любое значение в качестве зависимости
Для таких случаев есть InjectionToken. Его можно использовать, например, для передачи конфига в приложении.
Создаётся вот так:
import { InjectionToken } from '@angular/core';
const drink = new InjectionToken<string>('Напиток');А используется вот так:
providers: [{ provide: drink, useValue: 'Чай' }]Если вместо одной зависимости требуется массив, то используется свойство multi. Тогда значения не будут перезаписываться, а будут добавлять значения в массив.
providers: [
{ provide: drink, useValue: 'Чай', multi: true },
{ provide: drink, useValue: 'Вода', multi: true },
]
export class drinkComponent implements OnInit {
constructor(arr: drink) {}
ngOnInit() {
console.log(this.arr); // ['Чай', 'Вода']
}
}Надеюсь эта статья поможет вам лучше понять работу внедрения зависимостей :)
И, конечно, всем удачи в изучении такого зверя, как Angular😉