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 number
token
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()],});