Multiple implementations
Registering the same token more than once doesn’t overwrite anything: every registration is kept, in order. This is
how plugins, middleware chains, validators and event handlers are usually wired, and it matches .NET’s
IEnumerable<T> injection.
All of them, or the last one
Section titled “All of them, or the last one”import { class ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection, function createToken<T>(name: string): InjectionToken<T>
Creates a typed token.
createToken } from '@ylcnfrht/ioc';
interface interface Validator
Validator { Validator.validate(order: { total: number; items: number;}): string | undefined
validate(order: { total: number; items: number;}
order: { total: number
total: number; items: number
items: number }): string | undefined;}const const Validator: InjectionToken<Validator>
Validator = createToken<Validator>(name: string): InjectionToken<Validator>
Creates a typed token.
createToken<interface Validator
Validator>('Validator');
class class PositiveTotal
PositiveTotal implements interface Validator
Validator { PositiveTotal.validate(o: { total: number;}): "total must be positive" | undefined
validate(o: { total: number;}
o: { total: number
total: number }) { return o: { total: number;}
o.total: number
total > 0 ? var undefined
undefined : 'total must be positive'; }}class class NotEmpty
NotEmpty implements interface Validator
Validator { NotEmpty.validate(o: { items: number;}): "order is empty" | undefined
validate(o: { items: number;}
o: { items: number
items: number }) { return o: { items: number;}
o.items: number
items > 0 ? var undefined
undefined : 'order is empty'; }}
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addSingleton<Validator>(token: Token<Validator>, implementation: Newable<NoInfer<Validator>, []>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(const Validator: InjectionToken<Validator>
Validator, class PositiveTotal
PositiveTotal);const services: ServiceCollection
services.ServiceCollection.addSingleton<Validator>(token: Token<Validator>, implementation: Newable<NoInfer<Validator>, []>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(const Validator: InjectionToken<Validator>
Validator, class NotEmpty
NotEmpty);
const const provider: ServiceProvider
provider = const services: ServiceCollection
services.ServiceCollection.buildServiceProvider(options?: BuildOptions): ServiceProvider
Builds the provider. Registrations are copied, so changing this collection afterwards
does not affect providers already built.
buildServiceProvider();
const const all: Validator[]
all = await const provider: ServiceProvider
provider.ServiceProvider.getServices<Validator>(token: Token<Validator>): Promise<Validator[]>
Resolves every registration of token, in registration order. Empty if none.
getServices(const Validator: InjectionToken<Validator>
Validator); // [PositiveTotal, NotEmpty], registration orderconst const last: Validator
last = await const provider: ServiceProvider
provider.ServiceProvider.getRequiredService<Validator>(token: Token<Validator>): Promise<Validator>
Resolves token, throwing ServiceNotFoundError if it is not registered.
getRequiredService(const Validator: InjectionToken<Validator>
Validator); // NotEmpty: the last registration wins
const const errors: (string | undefined)[]
errors = const all: Validator[]
all.Array<Validator>.map<string | undefined>(callbackfn: (value: Validator, index: number, array: Validator[]) => string | undefined, thisArg?: any): (string | undefined)[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
map((v: Validator
v) => v: Validator
v.Validator.validate(order: { total: number; items: number;}): string | undefined
validate({ total: number
total: 0, items: number
items: 0 })).Array<string | undefined>.filter(predicate: (value: string | undefined, index: number, array: (string | undefined)[]) => unknown, thisArg?: any): (string | undefined)[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
filter(var Boolean: BooleanConstructor
Boolean);getServices(token)resolves every plain registration in registration order. It returns[]when there is none.getService/getRequiredService(and dependency injection through[deps]) use the last registration. That makes “register a default, let the app register its own later” work without removing anything.- Keyed registrations are never part of
getServices; see Keyed services.
Each registration has its own instance
Section titled “Each registration has its own instance”Two registrations are two services, even when they use the same class and lifetime. Two singleton registrations give two singletons; two scoped registrations give two instances per scope:
class class Handler
Handler {}
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addScoped<unknown>(token: Token<unknown>, implementation: Newable<unknown, []>): ServiceCollection (+4 overloads)
Registers a scoped service: one instance per scope. Same forms as
addSingleton
.
addScoped('handlers', class Handler
Handler);const services: ServiceCollection
services.ServiceCollection.addScoped<unknown>(token: Token<unknown>, implementation: Newable<unknown, []>): ServiceCollection (+4 overloads)
Registers a scoped service: one instance per scope. Same forms as
addSingleton
.
addScoped('handlers', class Handler
Handler);
const const provider: ServiceProvider
provider = const services: ServiceCollection
services.ServiceCollection.buildServiceProvider(options?: BuildOptions): ServiceProvider
Builds the provider. Registrations are copied, so changing this collection afterwards
does not affect providers already built.
buildServiceProvider();await using await using scope: ServiceProvider
scope = const provider: ServiceProvider
provider.ServiceProvider.createScope(): ServiceProvider
Creates a scope: scoped services resolve once per scope. Dispose it when done (or use await using).
createScope();
const [const a: unknown
a, const b: unknown
b] = await await using scope: ServiceProvider
scope.ServiceProvider.getServices<unknown>(token: Token<unknown>): Promise<unknown[]>
Resolves every registration of token, in registration order. Empty if none.
getServices('handlers');const a: unknown
a === const b: unknown
b; // false: one instance per registration(await await using scope: ServiceProvider
scope.ServiceProvider.getService<unknown>(token: Token<unknown>): Promise<unknown>
Resolves token, or returns undefined if it is not registered. With several registrations, the last one wins.
getService('handlers')) === const b: unknown
b; // true: the last registration's scoped instanceThe lifetime still applies per registration: calling getServices twice in the same scope returns the same scoped
instances, and in another scope, new ones.
Injecting the whole list
Section titled “Injecting the whole list”Dependency arrays inject one service per token. To give a class every implementation, use a factory:
class class PluginHost
PluginHost { constructor(private readonly PluginHost.plugins: Plugin[]
plugins: interface Plugin
Plugin[]) {} async PluginHost.startAll(): Promise<void>
startAll() { for (const const p: Plugin
p of this.PluginHost.plugins: Plugin[]
plugins) await const p: Plugin
p.Plugin.start(): Promise<void>
start(); }}
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addSingleton<Plugin>(token: Token<Plugin>, factory: ServiceFactory<NoInfer<Plugin>>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(const Plugin: InjectionToken<Plugin>
Plugin, () => ({ Plugin.name: string
name: 'metrics', Plugin.start(): Promise<void>
start: async () => {} }));const services: ServiceCollection
services.ServiceCollection.addSingleton<Plugin>(token: Token<Plugin>, factory: ServiceFactory<NoInfer<Plugin>>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(const Plugin: InjectionToken<Plugin>
Plugin, () => ({ Plugin.name: string
name: 'audit', Plugin.start(): Promise<void>
start: async () => {} }));const services: ServiceCollection
services.ServiceCollection.addSingleton<PluginHost>(token: Token<PluginHost>, factory: ServiceFactory<NoInfer<PluginHost>>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(class PluginHost
PluginHost, async (p: ServiceProvider
p) => new constructor PluginHost(plugins: Plugin[]): PluginHost
PluginHost(await p: ServiceProvider
p.ServiceProvider.getServices<Plugin>(token: Token<Plugin>): Promise<Plugin[]>
Resolves every registration of token, in registration order. Empty if none.
getServices(const Plugin: InjectionToken<Plugin>
Plugin)));Related
Section titled “Related”- Service management:
tryAdd*to register only if nothing is there yet - Keyed services: pick one implementation by key
- Registration