angular
June 8

Angular Injection Context

Angular в последних версиях представил функцию inject(), которая позволяет получить ссылку на провайдер функциональным способом, а не используя метод Injector.get(). Однако, если вы использовали её или библиотеку, которая использует её под капотом, вы могли столкнуться с следующей ошибкой:

inject() must be called from an injection context
such as a constructor, a factory function, a field initializer,
or a function used with `runInInjectionContext`.

Angular имеет две глобальные переменные, которые могут содержать инжектор в определенный момент времени: одна для Injector и одна для NodeInjector. Вот фрагменты кода для обеих:

let _currentInjector = undefined;

export function getCurrentInjector() {
  return _currentInjector;
}

export function setCurrentInjector(injector: Injector|undefined|null {
  const former = _currentInjector;
  _currentInjector = injector;
  return former;
}
let _injectImplementation

export function getInjectImplementation() {
  return _injectImplementation;
}

В Angular v16 была введена новая функция под названием assertInInjectionContext, которая проверяет, выполняется ли текущий стек вызовов в контексте инъекции:

export function assertInInjectionContext(debugFn: Function): void {
  if (!getInjectImplementation() && !getCurrentInjector()) {
    throw new RuntimeError(
        RuntimeErrorCode.MISSING_INJECTION_CONTEXT,
        ngDevMode &&
            (debugFn.name +
             '() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`'));
  }
}

Функция выдаст ошибку, если инжектор недоступен. Например:

import {
  ElementRef,
  assertInInjectionContext,
  inject,
} from '@angular/core';

export function injectNativeElement<T extends Element>(): T {
  assertInInjectionContext(injectNativeElement);

  return inject(ElementRef).nativeElement;
}

Рассмотрим случаи, в которых мы можем использовать функцию inject():

- В функции-фабрике, указанной для InjectionToken:

export const FOO = new InjectionToken('FOO', {
  factory() {
    const value = inject(SOME_PROVIDER);
    return ...
  },
});

- В функции-фабрике,, указанной для useFactory провайдера или в @Injectable.

@Component({
  providers: [
    {
      provide: FOO,
      useFactory() {
        const value = inject(SOME_PROVIDER);
        return ...
      },
    },
  ]
})
export class FooComponent {}

- Во время создания класса, который создается системой DI, такого как @Injectable или @Component, через конструктор или в инициализаторе для полей таких классов.

@Component({})
export class FooComponent {
  foo = inject(FOO);

  constructor(private ngZone: NgZone = inject(NgZone)) {
    const bar = inject(BAR);
  }
}

Исходный код показывает, что Angular вызывает функцию getNodeInjectable прямо перед созданием компонента и устанавливает текущий инжектор, который в данном случае является инжектором узла компонента.


- Внутри функции, которая выполняется с использованием функции runInInjectionContext.

mport { runInInjectionContext } from '@angular/core';

export class FooComponent {
  ngOnInit() {
    runInInjectionContext(this.injector, () => {
      console.log(
        'I can access the NodeInjector using inject()',
        inject(ElementRef)
      );
    })
}

Рассмотрев исходный код этой функции, мы можем определить, что она назначает переданный инжектор глобальной переменной инжектора, с которой мы ранее сталкивались.

Следует отметить, что функцию inject можно использовать только синхронно и она не совместима с асинхронными обратными вызовами или await.