createInjectionToken
createInjectionToken is an abstraction over the creation of an InjectionToken and returns a tuple of [injectFn, provideFn, TOKEN]
Creating an InjectionToken is usually not a big deal but consuming the InjectionToken might be a bit of a chore/boilerplate if the project utilizes InjectionToken a lot.
import { createInjectionToken } from 'ngxtension/create-injection-token';Usage
function countFactory() { return signal(0);}
export const [ injectCount, /* provideCount */ /* COUNT */] = createInjectionToken(countFactory);
@Component({})export class Counter { count = injectCount(); // WritableSignal<number> /* count = inject(COUNT); // WritableSignal<number> */}CreateInjectionTokenOptions
createInjectionToken accepts a second argument of type CreateInjectionTokenOptions which allows us to customize the InjectionToken that we are creating:
export interface CreateInjectionTokenOptions<T = unknown> { isRoot?: boolean; deps?: unknown[]; extraProviders?: Provider[]; multi?: boolean; token?: InjectionToken<T>;}isRoot
By default, createInjectionToken creates a providedIn: 'root' token so we do not have to provide it anywhere to use it. To create a non-root token, pass in isRoot: false
export const [injectCount, provideCount] = createInjectionToken(countFactory, { isRoot: false,});
@Component({ providers: [provideCount()],})export class Counter { count = injectCount(); // WritableSignal<number> /* count = inject(COUNT); // WritableSignal<number> */}deps
More than often, InjectionToken can depend on other InjectionToken. This is where deps come in and what we can pass in deps depends on the signature of the factoryFn that createInjectionToken accepts.
export const [, , DEFAULT] = createInjectionToken(() => 5);
function countFactory(defaultValue: number) { return signal(defaultValue);}
export const [injectCount, provideCount] = createInjectionToken(countFactory, { isRoot: false, deps: [DEFAULT],});extraProviders
We can also pass in other providers, via extraProviders, to createInjectionToken so when we call provideFn(), we provide those providers as well.
import { bookReducer } from './book.reducer';import * as bookEffects from './book.effects';
export const [injectBookService, provideBookService] = createInjectionToken( () => { const store = inject(Store); /* ... */ }, { isRoot: false, extraProviders: [ provideState('book', bookReducer), provideEffects(bookEffects), ], },);
// routes.tsexport const routes = [ { path: 'book/:id', // 👇 will also provideState() and provideEffects providers: [provideBookService()], loadComponent: () => import('./book/book.component'), },];multi
As the name suggested, we can also create a multi InjectionToken via createInjectionToken by passing multi: true to the CreateInjectionTokenOptions.
const [injectFn, provideFn] = createInjectionToken(() => 1, { multi: true });
const values = injectFn(); // number[] instead of number
provideFn(value);// 👆 this STILL accepts a numbertoken
If we already have an InjectionToken that we want to turn into injectFn and provideFn, we can pass that token, via token, to createInjectionToken.
One use-case is if our factory has a dependency on the same InjectionToken (i.e: a hierarchical tree-like structure where child Service might optionally have a parent Service)
export const SERVICE = new InjectionToken<TreeService>('TreeService');
function serviceFactory(parent: TreeService | null) { /* */}
export const [injectService, provideService] = createInjectionToken( serviceFactory, { isRoot: false, deps: [[new Optional(), new SkipSelf(), SERVICE]], token: SERVICE, },);Note that if token is passed in and isRoot: true, createInjectionToken will throw an error.
createNoopInjectionToken
As the name suggested, createNoopInjectionToken is the same as createInjectionToken but instead of factory function, it accepts description and options. This is useful when we want to create a multi token but we do not have a factory function.
It also supports a generic type for the InjectionToken that it creates:
const [injectFn, provideFn] = createNoopInjectionToken<number, true>( 'description', { multi: true },);
injectFn(); // number[]provideFn(1); // accepts numberprovideFn(() => 1); // accepts a factory returning a number;Even though it’s meant for multi token, it can be used for non-multi token as well:
const [injectFn, provideFn] = createNoopInjectionToken<number>('description');injectFn(); // number;provideFn(1); // accepts numberprovideFn(() => 1); // accepts a factory returning a number;ProvideFn
createInjectionToken and createNoopInjectionToken returns a provideFn which is a function that accepts either a value or a factory function that returns the value.
In the case where the value of the token is a Function (i.e: NG_VALIDATORS is a multi token whose values are functions), provideFn accepts a 2nd argument to distinguish between a factory function or a function as value
const [injectFn, provideFn] = createInjectionToken(() => { // this token returns Function as value return () => 1;});
// NOTE: this is providing the function value as-isprovideFn(() => 2, true);// NOTE: this is providing the function as a factoryprovideFn(() => () => injectDepFn(), false);By default, provideFn will treat the function as a factory function. If we want to provide the function as-is, we need to pass in true as the second argument.
Custom Injector
The injectFn returned by createInjectionToken also accepts a custom Injector to allow the consumers to call the injectFn
outside of an Injection Context.
function countFactory() { return signal(1);}
export const [injectCount] = createInjectionToken(countFactory);
@Component()export class Counter { #injector = inject(Injector);
ngOnInit() { const counter = injectCount({ injector: this.#injector }); }}Environment Initializer
Sometimes, it is required for root tokens to be initialized in ENVIRONMENT_INITIALIZER. Instead of providing ENVIRONMENT_INITIALIZER manually, we can retrieve the initializer provider function from createInjectionToken to do so.
const [ injectOne /* skip provider fn */ /* skip the token */, , , provideOneInitializer,] = createInjectionToken(() => 1);
bootstrapApplication(App, { providers: [provideOneInitializer()],});