injectree

Simple dependency injection library.

Usage no npm install needed!

<script type="module">
  import injectree from 'https://cdn.skypack.dev/injectree';
</script>

README

Injectree · GitHub license npm version Build Status codecov


A simple and intuitive dependency injection library supporting containers chaining.

Example

import { Injector, Token, provider } from 'injectree';

class Greeter {
  constructor(private readonly name: string) {}
  greet() {
    return `Hello ${this.name}!`;
  }
}
const NAME = new Token<string>();
const injector = new Injector([
    provider(NAME, {value: 'World'}),
    provider(Greeter, {deps: [NAME]}),
]);

const greeter = injector.get(Greeter);
greeter.greet(); // returns 'Hello World!'

Provider types

Value provider

Provides a value directly.

Example:

const TOKEN = new Token<string>('TOKEN');
const injector = new Injector([
  provider(TOKEN, {value: 'Hello!'}),
]);

Factory provider

Provides a value returned form the factory function. Can specify dependencies that are provided to the factory function as parameters.

Example:

const TOKEN = new Token<string>('TOKEN');
const NAME = new Token<string>('NAME');
const injector = new Injector([
  provider(TOKEN, {factory: (name: string) => `Hello ${name}!`, deps: [NAME]}),
  provider(NAME, {value: 'World'}),
]);

Class provider

Provides an instance of the class. Can specify dependencies that are provided to the constructor as parameters.

Example:

class Greeter {
  constructor(private name: string) {}
  greet() {
    return `Hello ${this.name}!`;
  }
}
const NAME = new Token<string>('NAME');
const injector = new Injector([
  provider(Greeter, {class: Greeter, deps: [NAME]}),
  provider(NAME, {value: 'World'}),
]);
// or
const injector = new Injector([
  provider(Greeter, {deps: [NAME]}), // class derived from the provider token
  provider(NAME, {value: 'World'}),
]);

Token provider

Provides a value/instance provided under another token.

Example:

const TOKEN = new Token<string>('TOKEN');
const OTHER_TOKEN = new Token<string>('OTHER_TOKEN');
const injector = new Injector([
  provider(OTHER_TOKEN, {value: 'Hello!'}),
  provider(TOKEN, {token: OTHER_TOKEN}),
]);

Default providers

The defaultProvider function allows for specifying providers for tokens that are not explicitly defined on any injector. Note that the default provider needs to be defined before the injector is created.

Example:

class Service {
  getValue() {
    return 42;
  }
}
defaultProvider(Service, {deps: []});

// later

const injector = new Injector();
const service = injector.get(Service);
service.getValue(); // return 42

Multi tokens

A multi token can hold bindings to multiple values and/or instances. Multiple providers can be specified for a single multi token.

Example:

const MULTI_TOKEN = new MultiToken<string>('MULTI_TOKEN');
defaultProvider(MULTI_TOKEN, {value: 'default'});
const injector = new Injector([
  provider(MULTI_TOKEN, {value: 'first'}),
  provider(MULTI_TOKEN, {value: 'second'}),
]);

injector.get(MULTI_TOKEN); // returns ['default', 'first', 'second']

Injectors chaining

An injector can specify its parent injector when it's created. If a provider is not found in the injector and the parent injector is specified, the search will be continued in the parent injector. In case of multi token, values and/or instances will be returned also from the parent injector.

Injection options

Injection options can be used to control the resolution process. They can be specified on the Injector.get() method or on the dep function when declaring dependencies on factory and class providers.

optional

If set to true, the injector will return undefined if no provider was defined for the token instead of throwing an error.

Example:

const TOKEN = new Token<string>('TOKEN');
const injector = new Injector();
const value: string | undefined = injector.get(TOKEN, {optional: true});
const TOKEN = new Token<string>('TOKEN');
class Service {
  constructor(readonly value: string | undefined) {}
}
const injector = new Injector([
  provider(Service, {deps: [dep(TOKEN, {optional: true})]}),
]);

from

Specifies where to search for the provider.

  • self-and-ancestors (default) - search in the current injector or any of it's ancestors,
  • self - search only in the current injector,
  • ancestors - search only in ancestor injectors.

Instances lifecycle

An instance is created right after it's resolved using the Injector.get() method (either directly or as a transitive dependency). Every successive resolution for the same token will result with the same instance being returned. An instance remains active until the injector that created it is destroyed. In case of instances created using the class provider, the [onDestroy] hook is available to give an instance a chance to clean up after itself.

Example:

import {Injector, provider, onDestroy} from 'injectree';

class Service {
  private unsubscribe: () => void;
  constructor() {
    this.unsubscribe = subscribeToSomeDataSource();
  }
  
  [onDestroy]() {
    this.unsubscribe();
    this.unsubscribe = undefined;
  }
}
const injector = new Injector([
    provider(Service, {deps: []}),
])
injector.get(Service); // Service created

// later
injector.destroy(); // Service[onDestroy] called