В Angular управление зависимостями и их инъекция реализуется через систему инжекторов. Иерархические инжекторы — это одна из ключевых особенностей механизма Dependency Injection (DI), которая предоставляет возможность для создания зависимостей с разной областью видимости, что значительно повышает гибкость и масштабируемость приложений.
Инжектор — это механизм, который управляет созданием и предоставлением объектов. Когда компонент или сервис запрашивает зависимость, инжектор решает, как и откуда эту зависимость получить.
Иерархия инжекторов в Angular состоит из нескольких уровней:
Корневой инжектор — создается на уровне корневого модуля приложения и является глобальным для всего приложения. Все сервисы, предоставленные корневым инжектором, доступны для всех компонентов и сервисов приложения.
Инжектор компонента — каждый компонент в Angular может иметь свой собственный инжектор. Это позволяет инжектировать зависимости, которые будут жить только в контексте этого компонента и его дочерних компонентов.
Инжектор модуля — модули в Angular могут также иметь свои инжекторы, если это необходимо для разделения логики приложения.
Когда Angular запрашивает зависимость, инжектор сначала ищет её в своем контексте, затем по иерархии вверх, вплоть до корневого инжектора. Если зависимость найдена на одном уровне, она используется. Если на том уровне зависимости нет, процесс продолжается до тех пор, пока не будет найден нужный инжектор или не будет достигнут корневой инжектор.
Когда в Angular создается новый компонент, для этого компонента создается и свой собственный инжектор. Этот инжектор находится внутри иерархии инжекторов и может предоставлять зависимости, которые будут использоваться только в этом компоненте и его дочерних компонентах. Важно понимать, что компоненты не могут «видеть» зависимости родительских компонентов, если они явно не задекларированы для них.
Таким образом, инжектор компонента позволяет инкапсулировать зависимость и ограничить её область видимости, что дает значительные преимущества в модульности и тестируемости приложения. Это также позволяет, например, избежать ненужного повторного создания сервисов или данных для каждого компонента.
Инжекторы модуля предоставляют еще один уровень инкапсуляции. Сервисы, предоставленные в контексте модуля, доступны для всех компонентов и сервисов внутри этого модуля, но не доступны вне его.
Для предоставления зависимостей через инжектор модуля необходимо
использовать свойство providers в декораторе
@NgModule. Все сервисы, перечисленные в этом массиве, будут
инжектироваться в компоненты этого модуля.
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [MyService], // Сервис доступен во всей области видимости модуля
bootstrap: [AppComponent]
})
export class AppModule { }
Такой подход позволяет организовать логически независимые части приложения с возможностью управления зависимостями на уровне модуля.
Изоляция зависимостей. Иерархические инжекторы дают возможность изолировать зависимости в пределах конкретных компонентов или модулей, что позволяет избежать глобального состояния и непредсказуемых побочных эффектов.
Легкость тестирования. Благодаря ограниченной области видимости зависимостей тестирование отдельных компонентов становится проще. Для тестирования можно явно указать зависимости, необходимые только для этого компонента, и избежать вмешательства глобальных сервисов.
Оптимизация производительности. Инжекторы компонентов и модулей позволяют эффективно управлять временем жизни объектов. Например, если сервис должен быть создан только один раз в рамках компонента, это можно легко настроить, не загружая корневой инжектор.
Гибкость в настройке зависимостей. Использование иерархических инжекторов открывает возможности для конфигурации зависимостей, которые могут отличаться в разных частях приложения, при этом сохраняя централизованное управление.
Для более наглядного примера рассмотрим ситуацию, когда у нас есть сервис, который зависит от другого сервиса, но должен быть разным для разных компонентов.
@Injectable({
providedIn: 'root', // Сервис доступен во всей иерархии приложения
})
export class ParentService {
constructor() {
console.log('ParentService created');
}
}
@Component({
selector: 'app-parent',
template: `<app-child></app-child>`,
providers: [ParentService], // Сервис доступен только в этом компоненте и его потомках
})
export class ParentComponent {
constructor(private parentService: ParentService) {}
}
@Component({
selector: 'app-child',
template: '',
})
export class ChildComponent {
constructor(private parentService: ParentService) {
console.log(this.parentService); // Здесь будет отдельная инстанция, если сервис предоставлен на уровне компонента
}
}
В данном примере, ParentService будет предоставлен как
зависимость только для ParentComponent и его дочерних
компонентов. Это означает, что если сервис будет предоставлен и на
уровне дочернего компонента, то он будет иметь свою собственную
инстанцию.
Если бы ParentService был бы предоставлен в корневом
инжекторе или в модуле, то его инстанция была бы единой для всех
компонентов и сервисов приложения.
Иногда необходимо создать собственный инжектор для специфических
целей. Это можно сделать, используя низкоуровневые API Angular, такие
как Injector. Важно помнить, что инжекторы создаются и
управляются Angular, но кастомные инжекторы могут быть полезны, если
необходимо предоставить нестандартные зависимости или динамически
определять их на основе каких-либо условий.
Пример создания кастомного инжектора:
import { Injectable, Injector } from '@angular/core';
@Injectable()
export class CustomService {
constructor() {
console.log('CustomService created');
}
}
export class CustomInjector {
constructor(private injector: Injector) {}
getService(): CustomService {
return this.injector.get(CustomService);
}
}
Этот подход позволяет гибко управлять инжекцией зависимостей в Angular и использовать инжекторы в соответствии с требованиями приложения.
Иерархические инжекторы в Angular являются мощным инструментом для управления зависимостями, позволяя создавать и управлять зависимостями с различной областью видимости. Это дает возможности для улучшения модульности, тестируемости и производительности приложения.