InversifyJS
A lightweight IoC container written in TypeScript.
About
InversifyJS is a lightweight (4KB) inversion of control (IoC) container for TypeScript and JavaScript apps.
A IoC container uses a class constructor to identify and inject its dependencies.
InversifyJS has a friendly API and encourage the usage of the best OOP and IoC practices.
Motivation
JavaScript now supports object oriented (OO) programming with class based inheritance. These features are great but the truth is that they are also
dangerous.
We need a good OO design (SOLID, Composite Reuse, etc.) to protect ourselves from these threats. The problem is that OO design is difficult and that is exactly why we created InversifyJS.
InversifyJS is a tool that helps JavaScript developers to write code with a good OO design.
Philosophy
InversifyJS has been developed with 4 main goals:
-
Allow JavaScript developers to write code that adheres to the SOLID principles.
-
Facilitate and encourage the adherence to the best OOP and IoC practices.
-
Add as little runtime overhead as possible.
-
Provide a state of the art development experience.
Testimonies
Nate Kohari - Author of Ninject
"Nice work! I've taken a couple shots at creating DI frameworks for JavaScript and TypeScript, but the lack of RTTI really hinders things.
The ES7 metadata gets us part of the way there (as you've discovered). Keep up the great work!"
Installation
You can get the latest release and the type definitions using npm:
npm install inversify@2.0.0-beta.7 inversify-dts reflect-metadata --save
The InversifyJS type definitions are included in the inversify-dts npm package:
The reflect-metadata type definitions are included in the npm package:
Note: InversifyJS requires a modern JavaScript engine with support for the Promise, Reflect (with metadata) and Proxy objects.
If your environment don't support one of these you will need to import a shim or polyfill. Check out the
Environment support and polyfills page in the wiki to learn more.
InversifyJS requires the following TypeScript compilation options in your tsconfig.json
file:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
The Basics (TypeScript)
Let’s take a look to the basic usage and APIs of InversifyJS with TypeScript:
Step 1: Declare your interfaces
Our goal is to write code that adheres to the dependency inversion principle.
This means that we should "depend upon Abstractions and do not depend upon concretions".
Let's start by declaring some interfaces (abstractions).
interface INinja {
fight(): string;
sneak(): string;
}
interface IKatana {
hit(): string;
}
interface IShuriken {
throw(): string;
}
Step 2: Declare dependencies using the @injectable
& @inject
decorators
Let's continue by declaring some classes (concretions). The classes are implementations of the interfaces that we just declared. All the classes must be annotated with the @injectable
decorator.
When a class has a dependency on an interface we also need to use the @inject
decorator to define an identifier for the interface that will be available at runtime. In this case we will use the string literals "IKatana"
and "IShuriken"
as runtime identifiers.
Note: InversifyJS also support the usage of Classes and Symbols (continue reading to learn more about this).
import { injectable, inject } from "inversify";
import "reflect-metadata";
@injectable()
class Katana implements IKatana {
public hit() {
return "cut!";
}
}
@injectable()
class Shuriken implements IShuriken {
public throw() {
return "hit!";
}
}
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject("IKatana") katana: IKatana,
@inject("IShuriken") shuriken: IShuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
Step 3: Create and configure a Kernel
We recommend to do this in a file named inversify.config.ts
. This is the only place in which there is some coupling.
In the rest of your application your classes should be free of references to other classes.
import { Kernel } from "inversify";
import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";
var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);
export default kernel;
Step 4: Resolve dependencies
You can use the method get<T>
from the Kernel
class to resolve a dependency.
Remember that you should do this only in your composition root
to avoid the service locator anti-pattern.
import kernel = from "./inversify.config";
var ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("cut!");
expect(ninja.sneak()).eql("hit!");
As we can see the IKatana
and IShuriken
were successfully resolved and injected into Ninja
.
The Basics (JavaScript)
It is recommended to use TypeScript for the best development experience but you can use plain JavaScript
if you preffer it. The following code snippet implements the previous example without TypeScript in Node.js v5.71:
var inversify = require("inversify");
require("reflect-metadata");
var TYPES = {
Ninja: "Ninja",
Katana: "Katana",
Shuriken: "Shuriken"
};
class Katana {
hit() {
return "cut!";
}
}
class Shuriken {
throw() {
return "hit!";
}
}
class Ninja {
constructor(katana, shuriken) {
this._katana = katana;
this._shuriken = shuriken;
}
fight() { return this._katana.hit(); };
sneak() { return this._shuriken.throw(); };
}
inversify.decorate(inversify.injectable(), Katana);
inversify.decorate(inversify.injectable(), Shuriken);
inversify.decorate(inversify.injectable(), Ninja);
inversify.decorate(inversify.inject(TYPES.Katana), Ninja, 0);
inversify.decorate(inversify.inject(TYPES.Shuriken), Ninja, 1);
var kernel = new inversify.Kernel();
kernel.bind(TYPES.Ninja).to(Ninja);
kernel.bind(TYPES.Katana).to(Katana);
kernel.bind(TYPES.Shuriken).to(Shuriken);
var ninja = kernel.get(TYPES.Ninja);
return ninja;
Features
Let's take a look to the InversifyJS features!
Support for classes
InversifyJS allows your classes to have a direct dependency on other classes. When doing so you will need to use the @injectable
decorator but you will not be required to use the @inject
decorator.
import { Kernel, injectable, inject } from "inversify";
@injectable()
class Katana {
public hit() {
return "cut!";
}
}
@injectable()
class Shuriken {
public throw() {
return "hit!";
}
}
@injectable()
class Ninja implements INinja {
private _katana: Katana;
private _shuriken: Shuriken;
public constructor(katana: Katana, shuriken: Shuriken) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
var kernel = new Kernel();
kernel.bind<Ninja>(Ninja).to(Ninja);
kernel.bind<Katana>(Katana).to(Katana);
kernel.bind<Shuriken>(Shuriken).to(Shuriken);
Support for Symbols
In very large applications using strings as the identifiers of the types to be injected by the InversifyJS can lead to naming collisions. InversifyJS supports and recommends the usage of Symbols instead of string literals.
A symbol is a unique and immutable data type and may be used as an identifier for object properties. The symbol object is an implicit object wrapper for the symbol primitive data type.
import { Kernel, injectable, inject } from "inversify";
let Symbols = {
INinja : Symbol("INinja"),
IKatana : Symbol("IKatana"),
IShuriken : Symbol("IShuriken")
};
@injectable()
class Katana implements IKatana {
public hit() {
return "cut!";
}
}
@injectable()
class Shuriken implements IShuriken {
public throw() {
return "hit!";
}
}
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject(Symbols.IKatana) katana: IKatana,
@inject(Symbols.IShuriken) shuriken: IShuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
var kernel = new Kernel();
kernel.bind<INinja>(Symbols.INinja).to(Ninja);
kernel.bind<IKatana>(Symbols.IKatana).to(Katana);
kernel.bind<IShuriken>(Symbols.IShuriken).to(Shuriken);
Declaring kernel modules
Kernel modules can help you to manage the complexity of your bindings in very large applications.
let warriors: IKernelModule = (k: IKernel) => {
k.bind<INinja>("INinja").to(Ninja);
};
let weapons: IKernelModule = (k: IKernel) => {
k.bind<IKatana>("IKatana").to(Katana).inTransientScope();
k.bind<IShuriken>("IShuriken").to(Shuriken).inSingletonScope();
};
kernel = new Kernel();
kernel.load(warriors, weapons);
Kernel snapshots
Declaring kernel snapshots is a feature that helps you to write unit tests with ease:
import { expect } from "chai";
import * as sinon from "sinon";
import kernel from "../../src/ioc/kernel";
describe("Ninja", () => {
beforeEach(() => {
kernel.snapshot();
});
afterEach(() => {
kernel.restore();
});
it("Ninja can fight", () => {
let katanaMock = {
hit: () => { return "hit with mock"; }
};
kernel.unbind("IKatana");
kernel.bind<ISomething>("IKatana").toConstantValue(katanaMock);
let ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("hit with mock");
});
it("Ninja can sneak", () => {
let shurikenMock = {
throw: () => { return "hit with mock"; }
};
kernel.unbind("IShuriken");
kernel.bind<ISomething>("IShuriken").toConstantValue(shurikenMock);
let ninja = kernel.get<INinja>("IShuriken");
expect(ninja.sneak()).eql("hit with mock");
});
});
Controlling the scope of the dependencies
InversifyJS uses transient scope by default but you can also use singleton scope:
kernel.bind<IShuriken>("IShuriken").to(Shuriken).inTransientScope();
kernel.bind<IShuriken>("IShuriken").to(Shuriken).inSingletonScope();
Injecting a constant or dynamic value
Binds an abstraction to a constant value:
kernel.bind<IKatana>("IKatana").toConstantValue(new Katana());
Binds an abstraction to a dynamic value:
kernel.bind<IKatana>("IKatana").toDynamicValue(() => { return new Katana(); });
Injecting a class constructor
Binds an abstraction to a class constructor.
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject("INewable<IKatana>") Katana: INewable<IKatana>,
@inject("IShuriken") shuriken: IShuriken
) {
this._katana = new Katana();
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
kernel.bind<INewable<IKatana>>("INewable<IKatana>").toConstructor<IKatana>(Katana);
Injecting a Factory
Binds an abstraction to a user defined Factory.
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject("IFactory<IKatana>") katanaFactory: () => IKatana,
@inject("IShuriken") shuriken: IShuriken
) {
this._katana = katanaFactory();
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
kernel.bind<IFactory<IKatana>>("IFactory<IKatana>").toFactory<IKatana>((context) => {
return () => {
return context.kernel.get<IKatana>("IKatana");
};
});
You can also define a Factory with args:
kernel.bind<IFactory<IWeapon>>("IFactory<IWeapon>").toFactory<IWeapon>((context) => {
return (throwable: boolean) => {
if (throwable) {
return context.kernel.getTagged<IWeapon>("IWeapon", "throwable", true);
} else {
return context.kernel.getTagged<IWeapon>("IWeapon", "throwable", false);
}
};
});
Sometimes you might need to pass arguments to a factory in different moments during the execution:
kernel.bind<IEngine>("IEngine").to(PetrolEngine).whenTargetNamed("petrol");
kernel.bind<IEngine>("IEngine").to(DieselEngine).whenTargetNamed("diesel");
kernel.bind<IFactory<IEngine>>("IFactory<IEngine>").toFactory<IEngine>((context) => {
return (named: string) => (displacement: number) => {
let engine = context.kernel.getNamed<IEngine>("IEngine", named);
engine.displacement = displacement;
return engine;
};
});
@injectable()
class DieselCarFactory implements ICarFactory {
private _dieselFactory: (displacement: number) => IEngine ;
constructor(
@inject("IFactory<IEngine>") factory: (category: string) => (displacement: number) => IEngine
) {
this._dieselFactory = factory("diesel");
}
public createEngine(displacement: number): IEngine {
return this._dieselFactory(displacement);
}
}
Auto factory
Binds an abstraction to a auto-generated Factory.
@injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
@inject("IFactory<IKatana>") katanaFactory: IFactory<IKatana>,
@inject("IShuriken") shuriken: IShuriken
) {
this._katana = katanaFactory();
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
kernel.bind<IFactory<IKatana>>("IFactory<IKatana>")
.toAutoFactory<IKatana>("IKatana");
Injecting a Provider (asynchronous Factory)
Binds an abstraction to a Provider. A provider is an asynchronous factory, this is useful when dealing with asynchronous I/O operations.
@injectable()
class Ninja implements INinja {
public katana: IKatana;
public shuriken: IShuriken;
public katanaProvider: IProvider<IKatana>;
public constructor(
@inject("IProvider<IKatana>") katanaProvider: IProvider<IKatana>,
@inject("IShuriken") shuriken: IShuriken
) {
this.katanaProvider = katanaProvider;
this.katana= null;
this.shuriken = shuriken;
}
public fight() { return this.katana.hit(); };
public sneak() { return this.shuriken.throw(); };
}
kernel.bind<IProvider<IKatana>>("IProvider<IKatana>").toProvider<IKatana>((context) => {
return () => {
return new Promise<IKatana>((resolve) => {
let katana = context.kernel.get<IKatana>("IKatana");
resolve(katana);
});
};
});
var ninja = kernel.get<INinja>("INinja");
ninja.katanaProvider()
.then((katana) => { ninja.katana = katana; })
.catch((e) => { console.log(e); });
Activation handler
It is possible to add an activation handler for a type. The activation handler is invoked after a dependency has been resolved and before it is added to the cache (if singleton) and injected. This is useful to keep our dependencies agnostic of the implementation of crosscutting concerns like caching or logging. The following example uses a proxy to intercept one of the methods (use
) of a dependency (IKatana
).
interface IKatana {
use: () => void;
}
@injectable()
class Katana implements IKatana {
public use() {
console.log("Used Katana!");
}
}
interface INinja {
katana: IKatana;
}
@injectable()
class Ninja implements INinja {
public katana: IKatana;
public constructor(@inject("IKatana") katana: IKatana) {
this.katana = katana;
}
}
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana).onActivation((context, katana) => {
let handler = {
apply: function(target, thisArgument, argumentsList) {
console.log(`Starting: ${new Date().getTime()}`);
let result = target.apply(thisArgument, argumentsList);
console.log(`Finished: ${new Date().getTime()}`);
return result;
}
};
katana.use = new Proxy(katana.use, handler);
return katana;
});
let ninja = kernelget<INinja>();
ninja.katana.use();
> Starting: 1457895135761
> Used Katana!
> Finished: 1457895135762
Middleware
InversifyJS performs 3 mandatory operations before resolving a dependency:
- Annotation
- Planning
- Resolution
In some cases there will be some additional operations:
If we have configured some Middleware it will be executed at some point before ot after the planning, resolution and activation phases.
Middleware can be used to implement powerful development tools. This kind of tools will help developers to identify problems during the development process.
Please refer to the wiki to learn more about the middleware API.
Multi-injection
We can use multi-injection When two or more concretions have been bound to the an abstraction.
Notice how an array of IWeapon
is injected into the Ninja
class via its constructor thanks to the usage of the @multiInject
decorator:
interface IWeapon {
name: string;
}
@injectable()
class Katana implements IWeapon {
public name = "Katana";
}
@injectable()
class Shuriken implements IWeapon {
public name = "Shuriken";
}
interface INinja {
katana: IWeapon;
shuriken: IWeapon;
}
@injectable()
class Ninja implements INinja {
public katana: IWeapon;
public shuriken: IWeapon;
public constructor(
@multiInject("IWeapon") weapons: IWeapon[]
) {
this.katana = weapons[0];
this.shuriken = weapons[1];
}
}
We are binding Katana
and Shuriken
to IWeapon
:
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana);
kernel.bind<IWeapon>("IWeapon").to(Shuriken);
Tagged bindings
We can use tagged bindings to fix AMBIGUOUS_MATCH
errors when two or more
concretions have been bound to the an abstraction. Notice how the constructor
arguments of the Ninja
class have been annotated using the @tagged
decorator:
interface IWeapon {}
@injectable()
class Katana implements IWeapon {}
@injectable()
class Shuriken implements IWeapon {}
interface INinja {
katana: IWeapon;
shuriken: IWeapon;
}
@injectable()
class Ninja implements INinja {
public katana: IWeapon;
public shuriken: IWeapon;
public constructor(
@inject("IWeapon") @tagged("canThrow", false) katana: IWeapon,
@inject("IWeapon") @tagged("canThrow", true) shuriken: IWeapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
We are binding Katana
and Shuriken
to IWeapon
but a whenTargetTagged
constraint is added to avoid AMBIGUOUS_MATCH
errors:
kernel.bind<INinja>(ninjaId).to(Ninja);
kernel.bind<IWeapon>(weaponId).to(Katana).whenTargetTagged("canThrow", false);
kernel.bind<IWeapon>(weaponId).to(Shuriken).whenTargetTagged("canThrow", true);
Create your own tag decorators
Creating your own decorators is really simple:
let throwable = tagged("canThrow", true);
let notThrowable = tagged("canThrow", false);
@injectable()
class Ninja implements INinja {
public katana: IWeapon;
public shuriken: IWeapon;
public constructor(
@inject("IWeapon") @notThrowable katana: IWeapon,
@inject("IWeapon") @throwable shuriken: IWeapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
Named bindings
We can use named bindings to fix AMBIGUOUS_MATCH
errors when two or more concretions have
been bound to the an abstraction. Notice how the constructor arguments of the Ninja
class
have been annotated using the @named
decorator:
interface IWeapon {}
@injectable()
class Katana implements IWeapon {}
@injectable()
class Shuriken implements IWeapon {}
interface INinja {
katana: IWeapon;
shuriken: IWeapon;
}
@injectable()
class Ninja implements INinja {
public katana: IWeapon;
public shuriken: IWeapon;
public constructor(
@inject("IWeapon") @named("strong")katana: IWeapon,
@inject("IWeapon") @named("weak") shuriken: IWeapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
We are binding Katana
and Shuriken
to IWeapon
but a whenTargetNamed
constraint is
added to avoid AMBIGUOUS_MATCH
errors:
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana).whenTargetNamed("strong");
kernel.bind<IWeapon>("IWeapon").to(Shuriken).whenTargetNamed("weak");
Kernel.getAll(), Kernel.getNamed() & Kernel.getTagged()
The InversifyJS kernel provides some helpers to resolve multi-injections:
let kernel = new Kernel();
kernel.bind<IWeapon>("IWeapon").to(Katana);
kernel.bind<IWeapon>("IWeapon").to(Shuriken);
let weapons = kernel.getAll<IWeapon[]>("IWeapon");
Named bindings:
let kernel = new Kernel();
kernel.bind<IWeapon>("IWeapon").to(Katana).whenTargetNamed("japonese");
kernel.bind<IWeapon>("IWeapon").to(Shuriken).whenTargetNamed("chinese");
let katana = kernel.getNamed<IWeapon>("IWeapon", "japonese");
let shuriken = kernel.getNamed<IWeapon>("IWeapon", "chinese");
And tagged bindings:
let kernel = new Kernel();
kernel.bind<IWeapon>("IWeapon").to(Katana).whenTargetTagged("faction", "samurai");
kernel.bind<IWeapon>("IWeapon").to(Shuriken).whenTargetTagged("faction", "ninja");
let katana = kernel.getTagged<IWeapon>("IWeapon", "faction", "samurai");
let shuriken = kernel.getTagged<IWeapon>("IWeapon", "faction", "ninja");
Contextual bindings & @targetName
The @targetName
decorator is used to access the names of the constructor arguments from a
contextual constraint even when the code is compressed. The constructor(katana, shuriken) { ...
becomes constructor(a, b) { ...
after compression but thanks to @targetName
we can still
refer to the design-time names katana
and shuriken
at runtime.
interface IWeapon {}
@injectable()
class Katana implements IWeapon {}
@injectable()
class Shuriken implements IWeapon {}
interface INinja {
katana: IWeapon;
shuriken: IWeapon;
}
@injectable()
class Ninja implements INinja {
public katana: IWeapon;
public shuriken: IWeapon;
public constructor(
@inject("IWeapon") @targetName("katana") katana: IWeapon,
@inject("IWeapon") @targetName("shuriken") shuriken: IWeapon
) {
this.katana = katana;
this.shuriken = shuriken;
}
}
We are binding Katana
and Shuriken
to IWeapon
but a custom when
constraint is added to avoid AMBIGUOUS_MATCH
errors:
kernel.bind<INinja>(ninjaId).to(Ninja);
kernel.bind<IWeapon>("IWeapon").to(Katana).when((request: IRequest) => {
return request.target.name.equals("katana");
});
kernel.bind<IWeapon>("IWeapon").to(Shuriken).when((request: IRequest) => {
return request.target.name.equals("shuriken");
});
The target fields implement the IQueryableString
interface to help you to create your custom constraints:
interface IQueryableString {
startsWith(searchString: string): boolean;
endsWith(searchString: string): boolean;
contains(searchString: string): boolean;
equals(compareString: string): boolean;
value(): string;
}
We have included some helpers to facilitate the creation of custom constraints:
import { Kernel, traverseAncerstors, taggedConstraint, namedConstraint, typeConstraint } from "inversify";
let whenParentNamedCanThrowConstraint = (request: IRequest) => {
return namedConstraint("canThrow")(request.parentRequest);
};
let whenAnyAncestorIsConstraint = (request: IRequest) => {
return traverseAncerstors(request, typeConstraint(Ninja));
};
let whenAnyAncestorTaggedConstraint = (request: IRequest) => {
return traverseAncerstors(request, taggedConstraint("canThrow")(true));
};
The InversifyJS fluent syntax for bindings includes some already implemented common contextual constraints:
interface IBindingWhenSyntax<T> {
when(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>;
whenTargetNamed(name: string): IBindingOnSyntax<T>;
whenTargetTagged(tag: string, value: any): IBindingOnSyntax<T>;
whenInjectedInto(parent: (Function|string)): IBindingOnSyntax<T>;
whenParentNamed(name: string): IBindingOnSyntax<T>;
whenParentTagged(tag: string, value: any): IBindingOnSyntax<T>;
whenAnyAncestorIs(ancestor: (Function|string)): IBindingOnSyntax<T>;
whenNoAncestorIs(ancestor: (Function|string)): IBindingOnSyntax<T>;
whenAnyAncestorNamed(name: string): IBindingOnSyntax<T>;
whenAnyAncestorTagged(tag: string, value: any): IBindingOnSyntax<T>;
whenNoAncestorNamed(name: string): IBindingOnSyntax<T>;
whenNoAncestorTagged(tag: string, value: any): IBindingOnSyntax<T>;
whenAnyAncestorMatches(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>;
whenNoAncestorMatches(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>;
}
Property injection
InversifyJS supports property injection because sometimes constructor injection is not the best kind of injection pattern.
let kernel = new Kernel();
let inject = makePropertyInjectDecorator(kernel);
interface ISomeService {
count: number;
increment(): void;
}
@injectable()
class SomeService implements ISomeService {
public count: number;
public constructor() {
this.count = 0;
}
public increment() {
this.count = this.count + 1;
}
}
class SomeWebComponent {
@inject("ISomeService")
private _service: ISomeService;
public doSomething() {
let count = this._service.count;
this._service.increment();
return count;
}
}
kernel.bind<ISomeService>("ISomeService").to(SomeService);
let someComponent = new SomeWebComponent();
expect(someComponent.doSomething()).eql(0);
expect(someComponent.doSomething()).eql(1);
Property injection is quite different of constructor injection and has some limitations.
- The
@inject
decorator requires an instance of kernel. - Injection takes place the first time the property is accessed via its getter.
- The
@targetName
decorator is not supported. - The only supported contextual constraints are
whenTargetNamed
and whenTargetTagged
. - Property injection supports the
@named
and @tagged
decorators. - The function
Object.prototype.propertyIsEnumerable()
returns false for properties decorated with @inject
. This is caused because the declared class property is replaced by a new instance property once the injection takes place. The propertyIsEnumerable
function returns false
for properties that return false
for hasOwnProperty
.
class Warrior {
@injectNamed(TYPES.IWeapon, "not-throwwable")
@named("not-throwwable")
public primaryWeapon: IWeapon;
@injectNamed(TYPES.IWeapon, "throwwable")
@named("throwwable")
public secondaryWeapon: IWeapon;
}
class Warrior {
@injectTagged(TYPES.IWeapon, "throwwable", false)
@tagged("throwwable", false)
public primaryWeapon: IWeapon;
@injectTagged(TYPES.IWeapon, "throwwable", true)
@tagged("throwwable", true)
public secondaryWeapon: IWeapon;
}
- Property injection supports multi-injection.
let kernel = new Kernel();
let multiInject = makePropertyMultiInjectDecorator(kernel);
let TYPES = { IWeapon: "IWeapon" };
interface IWeapon {
durability: number;
use(): void;
}
@injectable()
class Sword implements IWeapon {
public durability: number;
public constructor() {
this.durability = 100;
}
public use() {
this.durability = this.durability - 10;
}
}
@injectable()
class WarHammer implements IWeapon {
public durability: number;
public constructor() {
this.durability = 100;
}
public use() {
this.durability = this.durability - 10;
}
}
class Warrior {
@multiInject(TYPES.IWeapon)
public weapons: IWeapon[];
}
kernel.bind<IWeapon>(TYPES.IWeapon).to(Sword);
kernel.bind<IWeapon>(TYPES.IWeapon).to(WarHammer);
let warrior1 = new Warrior();
expect(warrior1.weapons[0]).to.be.instanceof(Sword);
expect(warrior1.weapons[1]).to.be.instanceof(WarHammer);
Circular dependencies
InversifyJS is able to identify circular dependencies and will throw an exception to help you to identify the location of the problem if a circular dependency is detected:
Error: Circular dependency found between services: IKatana and INinja
Inheritance
interface IWarrior {
weapon: IWeapon;
}
@injectable()
class Samurai implements IWarrior {
public weapon: IWeapon;
public constructor(weapon: IWeapon) {
this.weapon = weapon;
}
}
The constructor of a derived class must be manually implemented and annotated. Therefore, the following code snippet:
@injectable()
class SamuraiMaster extends Samurai implements IWarrior {
public isMaster: boolean;
}
Throws an exception:
Error: Derived class must explicitly declare its constructor: SamuraiMaster
However, he following works:
@injectable()
class SamuraiMaster extends Samurai implements IWarrior {
public isMaster: boolean;
public constructor(@inject(SYMBOLS.IWeapon) weapon: IWeapon) {
super(weapon);
this.isMaster = true;
}
}
The above also works with abstract
classes but it has one limitation. It doesn't work when a base class has constructor injections and its derived class don't have any constructor injections:
@injectable()
class Samurai implements ISamurai {
public rank: string;
public constructor(rank: string) {
this.rank = rank;
}
}
@injectable()
class SamuraiMaster extends Samurai implements ISamurai {
constructor() {
super("Master");
}
}
The precedding code snippet throws an error. Unfortunately, as a result of the technical limitation, this error is misleading:
Error: Derived class must explicitly declare its constructor: SamuraiMaster.
You can overcome this limitation by injecting into the derived class:
kernel.bind<string>(SYMBOLS.RANK).toConstantValue("Master");
@injectable()
class Samurai implements ISamurai {
public rank: string;
public constructor(rank: string) {
this.rank = rank;
}
}
@injectable()
class SamuraiMaster extends Samurai implements ISamurai {
constructor(@inject(SYMBOLS.RANK) rank: string) {
super(rank);
}
}
Please refer to the wiki for additional details.
Ecosystem
In order to provide a state of the art development experience we are also working on a series of middleware extensions and other development tools.
Please refer to the ecosystem page on the wiki to learn more about it.
Examples
Some integration examples are available in the official examples repository.
Support
If you are experience any kind of issues we will be happy to help. You can report an issue using the issues page or the chat. You can also ask questions at Stack overflow using the inversifyjs
tag.
If you want to share your thoughts with the development team or join us you will be able to do so using the official the mailing list. You can check out the
wiki and browse the documented source code to learn more about InversifyJS internals.
Acknowledgements
Thanks a lot to all the contributors, all the developers out there using InversifyJS and all those that help us to spread the word by sharing content about InversifyJS online. Without your feedback and support this project would not be possible.
License
License under the MIT License (MIT)
Copyright © 2015 Remo H. Jansen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.