Как работает Injector в Angular и что такое @Optional, @SkipSelf, @Host

от автора

Привет, Хабр!

В этой статье мы рассмотрим, как работает Injector в Angular, зачем нужны декораторы @Optional, @SkipSelf, @Host, и чем отличаются провайдеры на уровне root, модуля и компонента.

Как Angular находит зависимости: а он просто идёт вверх

В Angular нет ничего волшебного. Injector — это просто иерархическая система, которая ищет твою зависимость снизу вверх по дереву инжекторов.

AppInjector (корень приложения)   ├── ModuleInjector (фичевые модули)   │     └── ComponentInjector (каждый компонент)   │             └── DirectiveInjector (директивы/провайдеры внутри)

Angular смотрит: “Ага, тут у меня компонент. Он хочет MyService. Сначала проверю, есть ли у него локальный провайдер. Нет? Поднимусь выше — в модуль. Нет? Тогда в AppModule. Нашёл? Окей, вот.”

Простой пример:

@Injectable() export class LocalLogger {   log(msg: string) {     console.log('[LOCAL]', msg);   } }  @Component({   selector: 'logger-demo',   template: `<p>Check console</p>`,   providers: [LocalLogger] // локальный провайдер }) export class LoggerComponent {   constructor(logger: LocalLogger) {     logger.log('Hello from LoggerComponent');   } }

В этом случае LocalLogger создаётся прямо внутри компонента, и даже если выше по иерархии есть другой Logger, этот будет использоваться. Это поведение по дефолту. А вот дальше становится интересно…

Почему @Optional, @SkipSelf, @Host — очень точные указки

@Optional()

Если Angular не найдёт нужную зависимость, он бросит исключение. Но иногда нужно: «если есть — хорошо, если нет — не беда». Вот тогда и нужен @Optional().

@Component({   selector: 'optional-demo',   template: `...`, }) export class OptionalComponent {   constructor(@Optional() private config?: OptionalConfigService) {     if (config) {       config.apply();     } else {       console.warn('No OptionalConfigService found. Using defaults.');     }   } }

Здесь если OptionalConfigService нигде не зарегистрирован — просто будет undefined, и приложение не упадёт.

@SkipSelf()

А он говорит: “Ищи, но не у меня. Я — не в счёт. Начинай с родителя.”

Типовой кейс:

@Injectable() export class CoreService {}  @Component({   selector: 'child',   template: `...`,   providers: [CoreService] // этот экземпляр игнорируем }) export class ChildComponent {   constructor(@SkipSelf() core: CoreService) {     // будет взят CoreService из родителя, не из этого компонента   } }

Это суперполезно, когда хочется проксировать тот же сервис из родителя в потомков, не перебивая его локальной версией.

@Host()

Это уже более тонкий случай. @Host() говорит: “Остановись на компоненте, где используется эта директива. Выше — не лезь.”

@Directive({   selector: '[track-host]', }) export class TrackHostDirective {   constructor(@Host() private logger: LocalLogger) {     // найдёт только если LocalLogger в хост-компоненте, где повешена директива   } }

Это способ ограничить область поиска строго одним хостом. Выручает при написании сложных директив или компонентов, которые не должны зависеть от глобального контекста.

Уровни провайдеров

providedIn: ‘root’

@Injectable({ providedIn: 'root' }) export class ApiService {}

Это значит — «создай один экземпляр этого сервиса на всё приложение». Singleton. И Angular подсовывает его через корневой AppInjector.

Он будет создан только если реально используется.

В модуле

@NgModule({   providers: [AnalyticsService], }) export class AnalyticsModule {}

Теперь AnalyticsService создаётся при подключении этого модуля и будет один на весь модуль.

Но вот засада: если ты подключил этот модуль несколько раз — в каждом будет свой экземпляр сервиса.

В компоненте

@Component({   providers: [LocalStorageService], }) export class MyComponent {}

Каждый инстанс компонента — своя копия сервиса.

Создаем сервис, который работает ТОЛЬКО внутри компонента или модуля

Сервис, который живёт строго внутри компонента, — мощный паттерн. Можно создать форму, у которой есть FormContextService, но она не должна быть доступна извне.

@Injectable() export class FormContextService {   private state = new BehaviorSubject<any>({});   get value$() {     return this.state.asObservable();   }    setValue(val: any) {     this.state.next(val);   } }

Теперь повесим его только на компонент:

@Component({   selector: 'app-form',   providers: [FormContextService],   template: `     <input (input)="update($event.target.value)">     <child-component></child-component>   ` }) export class FormComponent {   constructor(private ctx: FormContextService) {}    update(val: string) {     this.ctx.setValue({ input: val });   } }

А вот child-component:

@Component({   selector: 'child-component',   template: `     <p *ngIf="val$ | async as val">{{ val.input }}</p>   ` }) export class ChildComponent {   val$ = this.ctx.value$;    constructor(private ctx: FormContextService) {} }

Оба компонента получают один и тот же экземпляр FormContextService, но он ограничен уровнем FormComponent. За его пределами его никто не увидит.

Если же мы захотим обойти это и получить контекст в родителе — придётся тащить @SkipSelf или @Host.

Вывод

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

Краткая выжимка:

  • providedIn: 'root' = singleton на всё приложение;

  • провайдер в модуле = singleton на модуль;

  • провайдер в компоненте = инстанс на каждый компонент;

  • @Optional = не паникуй, если не нашёл;

  • @SkipSelf = пропусти себя, иди вверх;

  • @Host = не выше хоста — ни шагу.

Пишите аккуратно и пишите осознанно, а так же делитесь своим опытом в комментарях.


Если вы хотите не просто использовать Angular, а понимать, как он работает изнутри — начните с основ внедрения зависимостей. Как работает Injector, зачем нужны @Optional, @SkipSelf и @Host, как управлять областью действия сервисов — обо всём этом мы подробно рассказали в новой статье.

А если хотите перейти от теории к практике — приходите на открытые уроки по Angular и фронтенд‑разработке. Это отличный способ увидеть процесс обучения изнутри, задать вопросы преподавателям и понять, подходит ли вам программа:

  1. Первый шаг в Angular — создаем приложение с нуля — 10 июля в 20:00

  2. Реактивное программирование в Angular — 24 июля в 20:00

Пройдите вступительное тестирование — узнайте, хватает ли ваших текущих знаний для обучения на курсе «Angular Developer».


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


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *