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.