README
Dependency-container
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`.