Понимание принципов 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 может вызвать конструктор компонента с этими сервиса в качестве аргументов.