TypeScript IoC Container

  • battle tested 💥
  • written on typescript
  • simple and lightweight ❤️
  • clean API 💚
  • supports tagged scopes
  • fully test covered 💯
  • can be used with decorators @inject
  • can inject properties
  • can inject lazy dependencies
  • composable and open to extend
Bash
npm install ts-ioc-container reflect-metadata
npm install ts-ioc-container reflect-metadata
Bash
yarn add ts-ioc-container reflect-metadata
yarn add ts-ioc-container reflect-metadata

Put this in your entrypoint file (should be the first line):

TypeScript
import 'reflect-metadata';
import 'reflect-metadata';

Configure tsconfig.json:

JSON
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Creating and using a container:

TypeScript __tests__/readme/basic.spec.ts
import { Container, type IContainer, inject, Registration as R, select } from '../../lib';

describe('Basic usage', function () {
  class Logger {
    name = 'Logger';
  }

  it('should inject dependencies', function () {
    class App {
      constructor(@inject('ILogger') public logger: Logger) {}
    }

    const container = new Container().addRegistration(R.fromClass(Logger).bindToKey('ILogger'));

    expect(container.resolve(App).logger.name).toBe('Logger');
  });

  it('should inject current scope', function () {
    const root = new Container({ tags: ['root'] });

    class App {
      constructor(@inject(select.scope.current) public scope: IContainer) {}
    }

    const app = root.resolve(App);

    expect(app.scope).toBe(root);
  });
});
import { Container, type IContainer, inject, Registration as R, select } from '../../lib';

describe('Basic usage', function () {
  class Logger {
    name = 'Logger';
  }

  it('should inject dependencies', function () {
    class App {
      constructor(@inject('ILogger') public logger: Logger) {}
    }

    const container = new Container().addRegistration(R.fromClass(Logger).bindToKey('ILogger'));

    expect(container.resolve(App).logger.name).toBe('Logger');
  });

  it('should inject current scope', function () {
    const root = new Container({ tags: ['root'] });

    class App {
      constructor(@inject(select.scope.current) public scope: IContainer) {}
    }

    const app = root.resolve(App);

    expect(app.scope).toBe(root);
  });
});

Documentation

Browse the documentation chapters in the navigation menu to learn about:

  • Overview - Core architecture and class diagrams
  • Container - Core container functionality, scopes, and instance management
  • Registration - Registering providers with keys and scopes
  • Provider - Dependency factories with singleton, args, visibility, alias, and decorator features
  • Injector - Different injection strategies (Metadata, Simple, Proxy)
  • Token - Token types for dependency keys (InjectionToken, SingleToken, GroupAliasToken, etc.)
  • Hooks - Lifecycle hooks for instance initialization and cleanup (onConstruct, onDispose)

Core Architecture

This chapter provides an overview of the core architecture and design of the TypeScript IoC container library. Understanding these concepts will help you make the most of the library's features.

The IoC container is built on four fundamental abstractions that work together to provide dependency injection:

Container

Manages the lifecycle and resolution of dependencies

Provider

Factory that creates dependency instances

Injector

Determines how dependencies are injected into constructors

Registration

Connects providers to containers with keys and configuration

Architecture Overview

graph TB Container[Container] Provider[IProvider] Injector[IInjector] Registration[IRegistration] Scope[Container Scope] Container -->|uses| Provider Container -->|uses| Injector Container -->|creates| Scope Registration -->|creates| Provider Container -->|manages| Registration Provider -->|resolves| Instance[Instance] Injector -->|injects| Instance style Container fill:#0366d6,color:#fff style Provider fill:#28a745,color:#fff style Injector fill:#ffc107,color:#000 style Registration fill:#17a2b8,color:#fff style Scope fill:#6f42c1,color:#fff style Instance fill:#dc3545,color:#fff

Class Diagram

The following diagram shows the main classes and interfaces and their relationships:

classDiagram class IContainer { +resolve(key) T +createScope(options) IContainer +register(key, provider) this +addRegistration(registration) this +dispose() void +getInstances() Instance[] +hasTag(tag) boolean } class Container { -parent: IContainer -scopes: IContainer[] -instances: Instance[] -providers: Map -injector: IInjector -tags: Set +resolve(key) T +createScope(options) IContainer +register(key, provider) this +dispose() void } class IProvider { +resolve(container, options) T +hasAccess(options) boolean +pipe(mappers) IProvider +setAccessRule(rule) this +setArgs(fn) this +lazy() this } class Provider { -factory: ResolveDependency -argsFn: ArgsFn -accessRule: ScopeAccessRule -isLazy: boolean +resolve(container, options) T +pipe(mappers) IProvider } class SingletonProvider { -cache: Cache +resolve(container, options) T } class IInjector { +resolve(container, constructor, options) T } class MetadataInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class SimpleInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class ProxyInjector { +resolve(container, constructor, options) T -createInstance(container, constructor, options) T } class IRegistration { +when(predicates) this +bindToKey(key) this +bindTo(key) this +pipe(mappers) this +applyTo(container) void } class Registration { -provider: IProvider -key: DependencyKey -scopeRules: ScopeMatchRule[] +when(predicates) this +bindToKey(key) this +applyTo(container) void } IContainer <|.. Container IContainer <|.. EmptyContainer Container --> IProvider : uses Container --> IInjector : uses Container --> IRegistration : manages IProvider <|.. Provider IProvider <|.. SingletonProvider IInjector <|.. MetadataInjector IInjector <|.. SimpleInjector IInjector <|.. ProxyInjector IRegistration <|.. Registration Registration --> IProvider : creates Container --> Container : parent-child