Всем привет???? Меня зовут Данила, фронтенд разработчик в ПСБ. Angular я начал изучать не так давно, поэтому часто встречаются сложные темы, которые непонятны и их нужно разбирать. Одной из таких тем и стало внедрение зависимостей (Dependency Injection). Что ж, давайте разбираться :)
Пару слов о DI
Внедрение зависимостей, или 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()
.
Свойства use*
Существует 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????