@moln/dependency-container

A lightweight dependency injection container for TypeScript/JavaScript for constructor injection.

Usage no npm install needed!

<script type="module">
  import molnDependencyContainer from 'https://cdn.skypack.dev/@moln/dependency-container';
</script>

README

Dependency-container

Build Status Coverage Status GitHub license npm

A lightweight dependency injection container for TypeScript/JavaScript for constructor injection.

Installation

Install by npm

npm install --save @moln/dependency-container

or install with yarn (this project is developed using yarn)

yarn add @moln/dependency-container

Modify your tsconfig.json to include the following settings

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

Add a polyfill for the Reflect API (examples below use reflect-metadata). You can use:

The Reflect polyfill import should only be added once, and before DI is used:

// main.ts
import "reflect-metadata";

// Your code here...

API

Decorators

injectable()

Class decorator factory that allows the class' dependencies to be injected at runtime.

usage
// foo.ts
import {injectable, createContainer} from "@moln/dependency-container";

@injectable()
export class Foo {
  constructor(private database: Database) {}
}

// some other file
const container = createContainer();

const instance = container.get(Foo);

inject()

Parameter decorator factory that allows for interface and other non-class information to be stored in the constructor's metadata.

usage
// foo.ts
import {injectable} from "@moln/dependency-container";

@injectable()
class Foo {
  constructor(@inject('bar') private bar: Bar) {}
}

class Bar {
  constructor(name: string) {}
}

// some other file
const container = createContainer();

container.register('bar', () => new Bar('abc'));

const instance = container.get(Foo);

DependencyContainer

InjectionToken

A token may be either a string, a symbol or a class constructor.

type InjectionToken<T = any> = constructor<T> | string | symbol;

Provider

Our container has the notion of a provider. A provider is registered with the DI container and provides the container the information needed to resolve an instance for a given token.

type FactoryFunction<T> = (
  container: DependencyContainerInterface,
  token: InjectionToken<T>
) => T;

interface Provider<T = any> {
  factory: FactoryFunction<T>;
  lifecycle: Lifecycle;
  instance?: T;
}

Registers

const container = new DependencyContainer();
container.register(Foo, () => new Foo()); // Register by factory
container.register(Foo, {factory: () => new Foo(), lifecycle: Lifecycle.SINGLETON}); // Full provider config

container.registerSingleton(Foo);
container.registerSingleton('MyFoo', Foo);

container.registerInstance(Bar, new Bar());
container.registerInstance('MyBar', new Bar());

Factories

reflectionFactory & reflectionFactoryFrom
const container = new DependencyContainer();

container.register(Foo, reflectionFactory);
container.register("MyBar", reflectionFactoryFrom(Bar));
aliasFactory
const container = new DependencyContainer();

container.register(Foo, () => new Foo);
container.register('aliasFoo', aliasFactory(Foo));
container.register('aliasFoo2', aliasFactory('aliasFoo'));

container.get(Foo) === container.get('aliasFoo')
ReflectionBasedAbstractFactory

Register (default) singleton reflection abstract factory.

const container = createContainer()

@injectable()
class Foo {
  constructor(private bar: Bar) {}
}

class Bar {
  constructor() {}
}

const instance = container.get(Foo)

Register transient reflection abstract factory.

const container = new DependencyContainer();
container.configure({
  abstractFactories: [[new ReflectionBasedAbstractFactory(), Lifecycle.TRANSIENT]],
});

@injectable()
class Foo {
  constructor(private bar: Bar) {}
}

class Bar {
  constructor() {}
}

const foo1 = container.get(Foo);
const foo2 = container.get(Foo);

foo1 !== foo2 // true

Middleware

When active the service. Middlewares will be call.

const container = new DependencyContainer();
const loggerMiddleware: ServiceMiddleware = (container, token, next) => {
 console.log('Start ' + (token.name || token))
 const instance = next();
 console.log('End ' + (token.name || token))
 return instance;
}

container.use(loggerMiddleware)

class Foo {}

container.registerSingleton(Foo)

const foo = container.get(Foo);
// console output:
// Start foo
// End foo

const foo = container.get(Foo); 
// Foo is singleton, never be call middleware.
matchMiddleware
const container = new DependencyContainer();
const loggerMiddleware: ServiceMiddleware = (container, token, next) => {
 console.log('Start ' + (token.name || token))
 const instance = next();
 console.log('End ' + (token.name || token))
 return instance;
}

container.use(matchMiddleware(Foo, [loggerMiddleware]))

class Foo {}
class Bar {}

container.registerSingleton(Foo)
container.registerSingleton(Bar)

const foo = container.get(Foo);
// console output:
// Start foo
// End foo

const bar = container.get(Bar); 
// Not matched, never be call `loggerMiddleware`.