Skip to content
Buy me a coffee

Registration

A ServiceCollection is a list of registrations. Each one says: for this token, build the service this way, with this lifetime. You fill the collection at startup, then call buildServiceProvider() to get a ServiceProvider that resolves services.

addSingleton, addScoped and addTransient accept the same arguments. Only the lifetime differs.

const
const services: ServiceCollection
services
= new
new ServiceCollection(): ServiceCollection

Collects service registrations, then builds a

ServiceProvider

. Modeled on .NET's IServiceCollection.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

ServiceCollection
();
// 1. A class with no constructor parameters is its own token.
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<Cache>(token: Newable<Cache, []>): 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 Cache
Cache
);
// 2. A class is its own token, with one dependency token per constructor parameter.
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<Scheduler, [clock: Clock]>(token: Newable<Scheduler, [clock: Clock]>, dependencies: readonly [clock: Token<Clock>]): 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 Scheduler
Scheduler
, [
const Clock: InjectionToken<Clock>
Clock
]);
// 3. A token and an implementation class.
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<Clock>(token: Token<Clock>, implementation: Newable<NoInfer<Clock>, []>): 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 Clock: InjectionToken<Clock>
Clock
,
class SystemClock
SystemClock
);
// 4. A token, an implementation and its dependencies.
const
const Audit: InjectionToken<AuditLog>
Audit
=
createToken<AuditLog>(name: string): InjectionToken<AuditLog>

Creates a typed token.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

createToken
<
class AuditLog
AuditLog
>('Audit');
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<AuditLog, [clock: Clock, cache: Cache]>(token: Token<AuditLog>, implementation: Newable<NoInfer<AuditLog>, [clock: Clock, cache: Cache]>, dependencies: readonly [clock: Token<Clock>, cache: Token<Cache>]): 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 Audit: InjectionToken<AuditLog>
Audit
,
class AuditLog
AuditLog
, [
const Clock: InjectionToken<Clock>
Clock
,
class Cache
Cache
]);
// 5. A token and a factory: any function that returns the instance (or a promise of it).
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<unknown>(token: Token<unknown>, factory: ServiceFactory<unknown>): 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
('startedAt', () => new
var Date: DateConstructor
new () => Date (+3 overloads)
Date
());

Every method returns the collection, so calls chain.

Form Use it when
add*(Class) / add*(Class, [deps]) The concrete class is what callers ask for.
add*(token, Impl) / add*(token, Impl, [deps]) Callers depend on an interface or base class; you pick the implementation here.
add*(token, factory) Construction needs logic: config, async setup, a third-party constructor. See Factories.
addValue(token, value) You already have the object: config, a constant, an instance built elsewhere.

The second argument is treated as an implementation only if it is written with class syntax. Any other function, arrow or function, is a factory and is called as factory(provider, key).

const
const services: ServiceCollection
services
= new
new ServiceCollection(): ServiceCollection

Collects service registrations, then builds a

ServiceProvider

. Modeled on .NET's IServiceCollection.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

ServiceCollection
();
const services: ServiceCollection
services
.
ServiceCollection.addSingleton<Database>(token: Token<Database>, factory: ServiceFactory<NoInfer<Database>>): 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 Database
Database
, (
provider: ServiceProvider
provider
) => new
constructor Database(url: string): Database
Database
(
function readDatabaseUrl(): string
readDatabaseUrl
()));

Factories get the provider, so they can resolve other services. They are covered in Factories.

addValue registers something that already exists. Any value is allowed, null and undefined included. Values behave like singletons but are never constructed and never disposed: the container didn’t create them, so it doesn’t own them.

interface
interface Config
Config
{
Config.port: number
port
: number;
Config.dbUrl: string
dbUrl
: string;
}
const
const Config: InjectionToken<Config>
Config
=
createToken<Config>(name: string): InjectionToken<Config>

Creates a typed token.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

createToken
<
interface Config
Config
>('Config');
const
const services: ServiceCollection
services
= new
new ServiceCollection(): ServiceCollection

Collects service registrations, then builds a

ServiceProvider

. Modeled on .NET's IServiceCollection.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

ServiceCollection
().
ServiceCollection.addValue<Config>(token: Token<Config>, value: NoInfer<Config>): ServiceCollection

Registers an existing value (config, constants, an instance you built). Any value works, null and undefined included. Never disposed by the container.

addValue
(
const Config: InjectionToken<Config>
Config
, {
Config.port: number
port
: 8080,
Config.dbUrl: string
dbUrl
: 'postgres://localhost/app' });

See Values and configuration.

Registrations don’t overwrite each other: a second add* for the same token is added to the list. getService and getRequiredService return the last one; getServices returns all of them in order. Each registration has its own instance, even when two registrations use the same class.

const
const Notifier: InjectionToken<Notifier>
Notifier
=
createToken<Notifier>(name: string): InjectionToken<Notifier>

Creates a typed token.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

createToken
<
interface Notifier
Notifier
>('Notifier');
const
const provider: ServiceProvider
provider
= new
new ServiceCollection(): ServiceCollection

Collects service registrations, then builds a

ServiceProvider

. Modeled on .NET's IServiceCollection.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

ServiceCollection
()
.
ServiceCollection.addSingleton<Notifier>(token: Token<Notifier>, implementation: Newable<NoInfer<Notifier>, []>): 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 Notifier: InjectionToken<Notifier>
Notifier
,
class EmailNotifier
EmailNotifier
)
.
ServiceCollection.addSingleton<Notifier>(token: Token<Notifier>, implementation: Newable<NoInfer<Notifier>, []>): 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 Notifier: InjectionToken<Notifier>
Notifier
,
class SmsNotifier
SmsNotifier
)
.
ServiceCollection.buildServiceProvider(options?: BuildOptions): ServiceProvider

Builds the provider. Registrations are copied, so changing this collection afterwards does not affect providers already built.

buildServiceProvider
();
await
const provider: ServiceProvider
provider
.
ServiceProvider.getRequiredService<Notifier>(token: Token<Notifier>): Promise<Notifier>

Resolves token, throwing ServiceNotFoundError if it is not registered.

getRequiredService
(
const Notifier: InjectionToken<Notifier>
Notifier
); // SmsNotifier (last wins)
await
const provider: ServiceProvider
provider
.
ServiceProvider.getServices<Notifier>(token: Token<Notifier>): Promise<Notifier[]>

Resolves every registration of token, in registration order. Empty if none.

getServices
(
const Notifier: InjectionToken<Notifier>
Notifier
); // [EmailNotifier, SmsNotifier]

That is how plugins and handlers are collected; see Multiple implementations. To register only if nothing is there yet, use tryAddSingleton / tryAddScoped / tryAddTransient:

// A library sets a default; the app's own registration, made earlier, wins.
const services: ServiceCollection
services
.
ServiceCollection.tryAddSingleton<Notifier>(token: Token<Notifier>, implementation: Newable<NoInfer<Notifier>, []>): ServiceCollection (+4 overloads)

Like

addSingleton

, but does nothing if token is already registered.

tryAddSingleton
(
const Notifier: InjectionToken<Notifier>
Notifier
,
class ConsoleNotifier
ConsoleNotifier
);

remove, replace and has edit the collection before it is built: see Service management. Keyed registrations (addKeyedSingleton and friends) are a separate namespace: see Keyed services.

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
({
BuildOptions.validateScopes?: boolean | undefined

Throw a ScopeError when a scoped service is resolved from the root provider or captured by a singleton. Recommended in development.

validateScopes
: true, // scoped services can't leak into singletons or the root
BuildOptions.validateOnBuild?: boolean | undefined

Check every registration up front (missing dependencies, captive scoped services) and throw a ValidationError.

validateOnBuild
: true, // every dependency must be registered
});

buildServiceProvider copies the registrations. Changing the collection afterwards does not affect providers already built, so you can build a provider, tweak the collection and build a second one (handy in tests). Both options are off by default; see Validation.