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.