There are many good reasons to use InversifyJS but we would like to highlight some of them:
InversifyJS offers you real decoupling. Consider the following class:
let TYPES = {
Ninja: Symbol.for("Ninja"),
Katana: Symbol.for("Katana"),
Shuriken: Symbol.for("Shuriken")
};
export { TYPES };
import { TYPES } from "./constants/types";
@injectable()
class Ninja implements Ninja {
private _katana: Katana;
private _shuriken: Shuriken;
constructor(
@inject(TYPES.Katana) katana: Katana,
@inject(TYPES.Shuriken) shuriken: Shuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
The Ninja
class will never point to the Katana
or Shuriken
classes. However,
it will point to the interfaces (at design-time) or Symbols (at run-time) which is
admissible because these are abstractions and
depending upon abstractions
is what DI is all about.
The InversifyJS container is the only element in the application aware of the life-cycle and dependencies.
We recommend to do this in a file named inversify.config.ts
and store the file in the root folder
that contains the application source code:
import { TYPES } from "./constants/types";
import { Katana } from "./entitites/katana";
import { Shuriken } from "./entitites/shuriken";
import { Ninja } from "./entitites/ninja";
container.bind<Katana>(TYPES.KATANA).to(Katana);
container.bind<Shuriken>(TYPES.SHURIKEN).to(Shuriken);
container.bind<Ninja>(TYPES.NINJA).to(Ninja);
This means that all the coupling in your application takes place in one unique place: the inversify.config.ts
file.
This is really important and we are going to prove it with an example.
Let's imagine that we are changing the difficulty in a game.
We just need to go to the inversify.config.ts
and change the Katana binding:
import { Katana } from "./entitites/SharpKatana";
if(difficulty === "hard") {
container.bind<Katana>(TYPES.KATANA).to(SharpKatana);
} else {
container.bind<Katana>(TYPES.KATANA).to(Katana);
}
You don't need to change the Ninja file!
The price to pay is the usage of Symbols or string literals but this price can be mitigated if you declare all your string literals in a file which contains constants (like actions in Redux). The good news is that in the future the symbols or string literals could end up being generated by the TS compiler, but that is in the hands of the TC39 committee for the moment.
Some "old" JavaScript IoC container like the angular 1.x $injector
have some problems:
InversifyJS solves these problems:
- There is support for transient and singleton scope.
- There are no namespace collisions thanks to tagged, named and contextual bindings.
- It is a stand alone library.
As far as I know it is the only IoC container for JavaScript that features complex dependency resolution (e.g. contextual bindings), multiple scopes (transient, singleton) and many other features. On top of that there is room for growth with features like interception or web worker scope. We also have plans for the development of dev-tools like browser extensions and middleware (logging, caching...).
You may think that you don't need an IoC container.
If the preceding argument is not enough you may want to read the following:
- The current state of dependency inversion in JavaScript
- About object-oriented design and the “class” & “extends” keywords in TypeScript / ES6
The library has been developed using TypeScript so type safety comes out of the box if you work
with TypeScript but it is nice to mention that if you try to inject a Katana into a class that
expects an implementation of Shuriken
you will get a compilation error.
We are working hard to provide you with a great IoC container for your JavaScript apps but also a great development experience. We have spend a lot of time trying to make the InversifyJS as user friendly as possible and are working on development tools for chrome and we have already developed a logger middleware to help you to debug in Node.js.