Всем привет???? Меня зовут Данила, фронтенд разработчик в ПСБ. 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 типа:

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

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

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

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

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

Свойства use*

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

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

  2. useExisting - в отличие от предшественника, использует уже созданный экземпляр (полезно для синглтонов, когда один экземпляр используется во всём приложении)

  3. useFactory - использует функцию для внедрения зависимости (удобно для переопределения логики, например, передать в аргументы функции сервис, получить из сервиса информацию и использовать её в конструкторе)

  4. 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????

Комментарии (0)