Someone on the Herding Code podcast No. 68, http://herdingcode.com/herding-code-68-new-year-shenanigans/, stated that IOC containers had no place with Python or Javascript,
I believe that IoC containers are necessary in large JavaScript applications. You can see that some popular JavaScript frameworks include an IoC container (e.g. the Angular $injector
).
I have develop an IoC container called InversifyJS you can learn more about it at http://inversify.io/.
Some JavaScript IoC containers out there declare the dependencies to be injected as the following:
import Katana from "./entitites/katana";
import Shuriken from "./entitites/shuriken";
@inject(Katana, Shuriken) // Wrong as Ninja is aware of Katana and Shuriken!
class Ninja {
constructor(katana: IKatana, shuriken: IShuriken) {
// ...
The nice thing about this approach is that there are no string literals. The bad thing about it is that our goal was to achieve decoupling and we just added a hard coded a reference to Katana and Shuriken to the file in which Ninja is declared and this is not real decoupling.
InversifyJS offers you real decoupling. The ninja.js file will never point to the katana or shuriken files. However, it will point to the interfaces (at design-time) or string literals (at run-time) which is admissible because these are abstractions and depending upon abstractions is what DI is all about.
import * as TYPES from "./constants/types";
@inject(TYPES.IKATANA, TYPES.ISHURIKEN) // Right as Ninja is aware of abstractions of Katana and Shuriken!
class Ninja {
constructor(katana: IKatana, shuriken: IShuriken) {
// ...
The InversifyJS kernel 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 * as TYPES from "./constants/types";
import Katana from "./entitites/katana";
import Shuriken from "./entitites/shuriken";
import Ninja from "./entitites/ninja";
kernel.bind(TYPES.IKATANA).to(Katana);
kernel.bind(TYPES.ISHURIKEN).to(Shuriken);
kernel.bind(TYPES.ININJA).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") {
kernel.bind(TYPES.IKATANA).to(SharpKatana);
} else {
kernel.bind(TYPES.IKATANA).to(Katana);
}
You don't need to change the Ninja file!
The price to pay is the 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 string literals could end up being generated by the TS compiler, but that is in the hands of the TC39 committee for the moment.
You can try it online here.