Понимание принципов Dependency Injection в Angular
Dependency Injection, или DI, являются одной из фундаментальных концепций в Angular. DI встроен во фреймворк и позволяет классам с декораторам конфигурировать необходимые им зависимости (например, Components, Directives, Pipes, and Injectables).
В системе DI существуют две основные роли: потребитель зависимостей (consumer) и поставщик зависимостей (provider).
Angular облегчает взаимодействие между потребителями и поставщиками зависимостей с помощью абстракции, называемой Injector
. Когда запрашивается зависимость, инжектор проверяет свой реестр, чтобы увидеть, есть ли уже там доступный экземпляр. Если нет, создается новый экземпляр и сохраняется в реестре. Angular создает Injector
для всего приложения (также известный как root injector
) во время инициализации приложения, а также любые другие инжекторы по мере необходимости (на уровне модулей или standalone-компонентов). В большинстве случаев вам не нужно вручную создавать инжекторы, но вы должны знать, что существует слой, который соединяет поставщиков и потребителей.
В этой теме рассматриваются основные сценарии того, как класс может выступать в роли зависимости. Angular также позволяет использовать функции, объекты, примитивные типы, такие как строка или логическое значение, или любые другие типы в качестве зависимостей.
Объявление зависимости
Предположим, есть класс с именем HeroService
, который должен выступать в качестве зависимости в компоненте. Первый шаг - добавить декоратор @Injectable
, чтобы показать, что класс может быть внедрен.
@Injectable() class HeroService {}
Далее необходимо сделать его доступным в DI, объявив его. Зависимость может быть предоставлена в нескольких местах:
- На уровне компонента, используя поле
providers
декоратора@Component
. В этом случаеHeroService
становится доступным для всех экземпляров этого компонента и других компонентов и директив, используемых в шаблоне. Например:
@Component({ standalone: true, selector: 'hero-list', template: '...', providers: [HeroService] }) class HeroListComponent {}
При регистрации провайдера на уровне компонента вы получаете новый экземпляр сервиса с каждым новым экземпляром этого компонента.
- Используйте поле
providers
объектаApplicationConfig
, переданного функцииbootstrapApplication
, чтобы объявить службу или другойInjectable
на уровне приложения. В этом случаеHeroService
доступен для всех компонентов, директив и каналов, объявленных в этомNgModule
или другомNgModule
, который находится в том жеModuleInjector
, который применим для этогоNgModule
. При регистрации провайдера вApplicationConfig
тот же экземпляр сервиса доступен всем применимым компонентам, директивам и пайпам. - Для приложений на основе
NgModule
используйте полеproviders
декоратора@NgModule
, чтобы объявить службу или другойInjectable
, доступный на уровне приложения.
// main.ts export const appConfig: ApplicationConfig = { providers: [ { provide: HeroService }, ] }; bootstrapApplication(AppComponent, appConfig)
- В корне приложения, что позволит внедрить его в другие классы в приложении. Это можно сделать, добавив поле
providedIn: 'root'
к декоратору@Injectable:
@Injectable({ providedIn: 'root' }) class HeroService {}
Когда вы объявляете сервис в корневом инжекторе, Angular создает единственный, общий экземпляр HeroService
и внедряет его в любой класс, который запрашивает его. Регистрация провайдера в метаданных @Injectable
также позволяет Angular оптимизировать приложение, удаляя сервис из скомпилированного приложения, если он не используется (tree-shaking).
Подключение зависимости
Наиболее распространенным способом внедрения зависимости является объявление ее в конструкторе класса. Когда Angular создает новый экземпляр компонента, директивы или пайпа, он определяет, какие сервиса или зависимости нужны этому классу, просматривая типы параметров конструктора. Например, если для HeroListComponent
требуется HeroService
, конструктор может выглядеть следующим образом:
@Component({ … }) class HeroListComponent { constructor(private service: HeroService) {} }
Или более современный вариант, с использованием функции Inject()
:
@Component({ … }) class HeroListComponent { private service = inject(HeroService); }
Когда Angular обнаруживает, что компонент зависит от сервиса, то сначала проверяет, есть ли у инжектора существующие экземпляры этого сервиса. Если запрошенного экземпляра сервиса еще нет, инжектор создает его с использованием зарегистрированного провайдера и добавляет его в инжектор.
Когда все запрошенные сервисы есть в инжекторе, Angular может вызвать конструктор компонента с этими сервиса в качестве аргументов.