Netanel Basal

Learn development with great articles

Understanding Angular Injection Context

--

Angular introduced the inject() function in recent versions, which allows us to obtain a reference to a provider in a functional way rather than using the Injector.get() method. However, if you have used it or a library that uses it under the hood, you may have encountered the following error:

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

An injection context is simply a term for “someone is using the inject() function but there is no injector available in the current stack frame."

Angular has two global variables that can hold an injector at a certain point in time: one for an Injector and one for NodeInjector. Here are the code snippets for both:

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;
}

In Angular v16, a new function named assertInInjectionContext was introduced that checks if the current stack frame is running inside an injection context:

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`'));
}
}

If no injector is available, it throws the error. We can use this function if we have code that relies on the inject() function and we want to verify that the consumer is using it where an injector is available. For example:

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

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

return inject(ElementRef).nativeElement;
}

Let’s go over the phases in which we can use the inject() function:

  • In the factory function specified for an InjectionToken:
export const FOO = new InjectionToken('FOO', {
factory() {
const value = inject(SOME_PROVIDER);
return ...
},
});
  • In the factory function specified for useFactory of a Provider or an @Injectable.

@Component({
providers: [
{
provide: FOO,
useFactory() {
const value = inject(SOME_PROVIDER);
return ...
},
},
]
})
export class FooComponent {}
  • During the construction of a class that is being instantiated by the DI system, such as an @Injectable or @Component, via the constructor or in the initializer for fields of such classes:
@Component({})
export class FooComponent {
foo = inject(FOO);

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

We can see in the source code that Angular calls the getNodeInjectable function right before instantiating the component and sets the current injector, which in this case, is the component node injector.

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

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

By examining the source code of this function, we can determine that it assigns the passed injector to the global injector variable that we previously encountered.

It’s worth noting that the inject function can only be used synchronously, and isn’t compatible with asynchronous callbacks or any await points.

Follow me on Medium or Twitter to read more about Angular and JS!

--

--

Netanel Basal
Netanel Basal
Netanel Basal
Netanel Basal

Written by Netanel Basal

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.

Responses (3)