Registration
Registrations connect providers to the container with keys and configuration. They can be created using decorators or the fluent API, providing flexibility in how you organize your dependency configuration.
Registration Flow
The following diagram shows how registrations are processed and applied to containers:
bindToKey, pipe, when] Config --> CreateProvider[Registration creates provider with configuration] CreateProvider --> AddToContainer[Developer addRegistration to container] AddToContainer --> Apply[Container applies Registration] Apply --> CheckScope{Scope matches?} CheckScope -->|Yes| Register[Register provider & alias] CheckScope -->|No| Skip[Skip registration] Register --> Complete([Registration complete]) Skip --> Complete classDef startEnd fill:#4A90E2,stroke:#2E5C8A,stroke-width:3px,color:#fff classDef process fill:#6C5CE7,stroke:#4834D4,stroke-width:2px,color:#fff classDef config fill:#00B894,stroke:#00A085,stroke-width:2px,color:#fff classDef decision fill:#FDCB6E,stroke:#E17055,stroke-width:2px,color:#2D3436 classDef success fill:#00B894,stroke:#00A085,stroke-width:2px,color:#fff classDef skip fill:#636E72,stroke:#2D3436,stroke-width:2px,color:#fff class Start,Complete startEnd class CreateProvider,AddToContainer,Apply process class Config config class CheckScope decision class Register success class Skip skip
Registration Methods
There are several ways to create and apply registrations:
- Decorator-based: Using
@registerdecorator - Fluent API: Using
Registrationclass methods - Direct registration: Using
container.register()
Decorator-Based Registration
The @register decorator provides a declarative way to register
classes. This is the most convenient approach for most use cases.
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
Fluent API Registration
The fluent API provides more control and is useful when you need dynamic registration or want to avoid decorators.
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
Keys
Keys identify dependencies in the container. They can be strings, symbols, or tokens. Understanding how keys work is essential for effective dependency management.
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
Scope Registration
Scope match rules (ScopeMatchRule) control which scopes a provider is available in. This
allows you to register different implementations for different
environments or contexts.
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
import {
bindTo,
Container,
DependencyMissingKeyError,
register,
Registration as R,
scope,
select as s,
singleton,
} from '../../lib';
describe('Registration module', function () {
const createContainer = () => new Container({ tags: ['root'] });
it('should register class', function () {
@register(bindTo('ILogger'), scope((s) => s.hasTag('root')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register value', function () {
const root = createContainer().addRegistration(R.fromValue('smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should register fn', function () {
const root = createContainer().addRegistration(R.fromFn(() => 'smth').bindToKey('ISmth'));
expect(root.resolve('ISmth')).toBe('smth');
});
it('should raise an error if key is not provider', () => {
expect(() => {
createContainer().addRegistration(R.fromValue('smth'));
}).toThrowError(DependencyMissingKeyError);
});
it('should register dependency by class name if @key is not provided', function () {
class FileLogger {}
const root = createContainer().addRegistration(R.fromClass(FileLogger));
expect(root.resolve('FileLogger')).toBeInstanceOf(FileLogger);
});
it('should assign additional key which redirects to original one', function () {
@register(bindTo('ILogger'), bindTo(s.alias('Logger')), singleton())
class Logger {}
const root = createContainer().addRegistration(R.fromClass(Logger));
expect(root.resolveByAlias('Logger')[0]).toBeInstanceOf(Logger);
expect(root.resolve('ILogger')).toBeInstanceOf(Logger);
});
});
Registration Pipeline
Registrations support the pipeline pattern, allowing you to chain multiple transformations:
import { args, singleton, scopeAccess } from 'ts-ioc-container';
const container = new Container()
.addRegistration(
R.fromClass(ConfigService)
.bindToKey('IConfig')
.when((s) => s.hasTag('root'))
.pipe(args('/config.json'))
.pipe(singleton())
.pipe(scopeAccess(({ invocationScope }) => invocationScope.hasTag('admin')))
);import { args, singleton, scopeAccess } from 'ts-ioc-container';
const container = new Container()
.addRegistration(
R.fromClass(ConfigService)
.bindToKey('IConfig')
.when((s) => s.hasTag('root'))
.pipe(args('/config.json'))
.pipe(singleton())
.pipe(scopeAccess(({ invocationScope }) => invocationScope.hasTag('admin')))
);Container Modules
Container modules encapsulate registration logic, making it easy to organize and compose your dependency configuration.
import { bindTo, Container, type IContainer, type IContainerModule, register, Registration as R } from '../../lib';
@register(bindTo('ILogger'))
class Logger {}
@register(bindTo('ILogger'))
class TestLogger {}
class Production implements IContainerModule {
applyTo(container: IContainer): void {
container.addRegistration(R.fromClass(Logger));
}
}
class Development implements IContainerModule {
applyTo(container: IContainer): void {
container.addRegistration(R.fromClass(TestLogger));
}
}
describe('Container Modules', function () {
function createContainer(isProduction: boolean) {
return new Container().useModule(isProduction ? new Production() : new Development());
}
it('should register production dependencies', function () {
const container = createContainer(true);
expect(container.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register development dependencies', function () {
const container = createContainer(false);
expect(container.resolve('ILogger')).toBeInstanceOf(TestLogger);
});
});
import { bindTo, Container, type IContainer, type IContainerModule, register, Registration as R } from '../../lib';
@register(bindTo('ILogger'))
class Logger {}
@register(bindTo('ILogger'))
class TestLogger {}
class Production implements IContainerModule {
applyTo(container: IContainer): void {
container.addRegistration(R.fromClass(Logger));
}
}
class Development implements IContainerModule {
applyTo(container: IContainer): void {
container.addRegistration(R.fromClass(TestLogger));
}
}
describe('Container Modules', function () {
function createContainer(isProduction: boolean) {
return new Container().useModule(isProduction ? new Production() : new Development());
}
it('should register production dependencies', function () {
const container = createContainer(true);
expect(container.resolve('ILogger')).toBeInstanceOf(Logger);
});
it('should register development dependencies', function () {
const container = createContainer(false);
expect(container.resolve('ILogger')).toBeInstanceOf(TestLogger);
});
});
Best Practices
- Use decorators for simple cases - When you have straightforward class registrations
- Use fluent API for dynamic registration - When keys or providers are determined at runtime
- Always provide keys for values/factories - Required to avoid errors
- Use modules for organization - Group related registrations together
- Use scope match rules for environment-specific code - Keep configurations clean
- Export tokens as constants - Create keys once and reuse them
- Use aliases for grouping - When you need multiple implementations
Registration Lifecycle
- Creation: Registration is created with a provider
- Configuration: Keys, scopes, and features are configured
- Application: Registration is applied to container via
addRegistration() - Validation: Scope match rules are evaluated
- Storage: Provider is stored in container's provider map
- Alias Registration: Aliases are registered if provided