Внедрение зависимостей в Angular простыми словами

от автора

Всем привет? Меня зовут Данила, фронтенд разработчик в ПСБ. Angular я начал изучать не так давно, поэтому часто встречаются сложные темы, которые непонятны и их нужно разбирать. Одной из таких тем и стало внедрение зависимостей (Dependency Injection). Что ж, давайте разбираться 🙂

Пару слов о DI

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

Простой пример — чай с сахаром. У нас есть чай, но без сахара он не такой вкусный, как с ним. Чтобы получить вкусный напиток, мы берём чайную ложку (инжектор), набираем ей сахар (зависимость) из сахарницы (провайдера) и кладём в кружку.

Сервисы

Чаще всего в 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 типа:

  1. @Optional() — необязательная зависимость (используется в случае, когда неизвестно, будет ли зависимость или нет)

  2. @Self() — ищет зависимость локально в модуле

  3. @SkipSelf() — ищет зависимость, начиная с родительского элемента (скоупа)

  4. @Host() — указывает, что если механизм DI дошёл до хоста, то нужно остановить поиск зависимости

    При необходимости, их можно миксовать. Но не все, нельзя смешивать @Host() и @Self(), а также @SkipSelf() и @Self().

Свойства use*

Существует 4 метода:

  1. useClass — как и писал ранее, создаёт экземпляр класса

  2. useExisting — в отличие от предшественника, использует уже созданный экземпляр (полезно для 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?


ссылка на оригинал статьи https://habr.com/ru/articles/751422/