injectable
A typed token with matching inject and provide functions, so you skip the InjectionToken boilerplate. Think of it as createContext for Angular.
@mmstack/dinpm
#Basic usage
injectable<T>(name) returns a [inject, provide, token] tuple. Name the first two at the call site, provide the value where you want it, and inject it anywhere below. Without a provider the injector returns null.
import { injectable } from '@mmstack/di';
const [injectLogger, provideLogger] = injectable<Logger>('Logger');
// provide it on a component or in a providers array
providers: [provideLogger({ log: (m) => console.log(m) })];
// inject it anywhere below
const logger = injectLogger(); // Logger | null#Factory providers
provide accepts a factory. Pass a dependency array when the factory needs other services. A zero-argument factory needs no array and can still call inject(), since it runs in an injection context.
const [injectApiConfig, provideApiConfig] = injectable<ApiConfig>('ApiConfig');
// with dependencies
provideApiConfig(
(http: HttpClient) => ({ baseUrl: '/api', timeout: 5000 }),
[HttpClient],
);
// zero-arg factory, still allowed to inject()
provideApiConfig(() => ({ baseUrl: inject(BASE_URL), timeout: 5000 }));#Fallbacks and required values
The options object changes what a missing provider does. Give a fallback for a default value, lazyFallback when the default needs inject() or is expensive, or errorMessage to make the value required. The reader's return type follows: T with a fallback, never null with an error message.
// default value
const [injectTheme] = injectable<Theme>('Theme', {
fallback: { primary: '#007bff' },
});
// lazily evaluated default (can inject, runs at most once per app)
const [injectTheme2] = injectable<Theme>('Theme', {
lazyFallback: () => ({ primary: inject(APP_PRIMARY) }),
});
// required, throws if not provided
const [injectApiKey] = injectable<string>('ApiKey', {
errorMessage: 'Provide it with provideApiKey().',
});#Providing a function value
provide treats any function as a factory. When the token's type is itself a function, wrap the value in a factory that returns it, or it gets called as one.
type Validator = (value: string) => boolean;
const [injectValidator, provideValidator] = injectable<Validator>('Validator');
provideValidator(() => isLongEnough); // factory returns the function
// provideValidator(isLongEnough); // would be called as a factory#The raw token
The third tuple element is the underlying InjectionToken, for deps arrays, Injector.create, or overriding in tests.
const [injectApi, provideApi, API_TOKEN] = injectable<ApiClient>('Api');
// use the token in a classic provider or a test
{ provide: OTHER, useFactory: (api) => new Other(api), deps: [API_TOKEN] }
TestBed.overrideProvider(API_TOKEN, { useValue: fakeApi });