After using TypeScript a lot with AngularJs and in separate projects I came to think about its dependency injection model and whether any Ioc containers had been created for TypeScript, A quick google surfaced a library called InversifyJs.

Adopting InversifyJs is easy and you pretty much hit the ground running if are you accustomed to using Ioc containers in C# for example, as the syntax doesn’t feel so alien when setting up the container. In this post, I’m gonna quickly run through using the library in a project I have created over here.  Feel free to clone the repository and follow along.

Go ahead and get into the top level of the project you just cloned in a terminal/prompt and run


npm install
gulp scripts
npm start

If everything works (which it should) then you should’ve seen my riveting project in action, you should see the word “Meow!” outputted. If you see this then great if you don’t, then lucky you, you could do something else more interesting.

folder structure for InversifyJs project

At this point open up the project in a code editor and have a look around, it should look like the above. At the moment, our application focusses solely on cats, hence the meow, which is great and all, but what about all the other animals? Let’s improve this and use InversifyJs to inject our animal dependencies wherever they are needed.

Before continuing, install InversifyJs with npm install inversify reflect-metadata --save.

Also, make these changes to the tsconfig.json in the project:


{
    "compilerOptions": {
        "target": "es5",
        "lib": ["es6"],
        "types": ["reflect-metadata"],
        "module": "commonjs",
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

AMAZING! Lets begin.

Firstly, we need to make an abstraction for our animals. With this in mind lets create an IAnimal interface, which all of our future animals must use, makes sense, right? Add an Animal.interface.ts file in the /animals folder and add this content.

animals/Animal.interface.ts


export interface IAnimal {

    makeNoise()
    
}

For the sake of simplicity, we will say that all animals make a noise, in the above interface we are defining a function that must be implemented when something (another animal) uses our interface (eg. a dog must make a noise, a horse must make a noise etc..).

Next stop, creating runtime identifiers and such. Add a folder in the top level called Ioc and add two files in there; inversify.config.ts and types.ts. Fill in the types.ts file with:


let types = {
    
        Animal: Symbol("Animal")
        
    };
    
    export { types };

Here we are providing type identifiers for our Animals, we use the Symbol type so that the types will not clash with one another, you may also use strings or classes.

Now, fill the inversify.config.ts file with this content:


import { Container } from "inversify";
import { types } from "./types";
import { IAnimal } from "../animals/animal.interface";
import { Cat } from "../animals/cat";

var appContainer = new Container();

appContainer.bind(types.Animal).to(Cat);

export default appContainer

Here we instantiate a new container and associate our IAnimal abstraction to the cat in this case such that, at runtime, whenever an IAnimal interface is found, an instance of a cat is injected. Although we aren’t quite finished yet, we still need to hook up a few other things so that the above happens correctly.

Head over to out Cat.ts file and change it so it looks like this


import { IAnimal } from './animal.interface';
import { injectable, inject } from "inversify";
import "reflect-metadata";


@injectable()
export class Cat implements IAnimal {

    makeNoise() {
        console.log("Meow!");
    }

}

You will notice 2 new things, one is that the cat class now implements the IAnimal interface we made and provides an implementation for its makeNoise() function. Two, we import a few modules from the InversifyJs library and decorate the class with the @Injectable() decorator. This allows the class to be injected at runtime.

We are now going to fill in the empty animal.proxy.ts file with this content:


import { IAnimal } from './animals/animal.interface';
import { injectable, inject } from "inversify";
import { types } from "./Ioc/types"
import "reflect-metadata";

export class AnimalProxy {

    private _animal: IAnimal;

    constructor(
        @inject(types.Animal) animal: IAnimal
    )
    {
        this._animal = animal;
    }

    makeNoise() {
        this._animal.makeNoise();
    }


}

The whole point of the proxy file is to take our animal, any kind of animal that implements the IAnimal interface and provide a universal set of actions for it (so just to make a noise in our case). In the constructor, we take an animal of type IAnimal and assign its value to a private field of the same type in the class, which can then be consumed by the functions in the class.

We mark the parameter in the constructor with the @inject() decorator. This allows InversifyJs to inject an instance of whatever the IAnimal interface resolves to, a cat in our case.

Finally we will edit the index.ts file and change its content to the following:


import appContainer from './Ioc/inversify.config';
import { types } from './Ioc/types';
import { AnimalProxy } from './animal.proxy';
import { IAnimal } from './animals/animal.interface';


let proxiedAnimal: AnimalProxy;

proxiedAnimal = new AnimalProxy(appContainer.get(types.Animal));

proxiedAnimal.makeNoise();

Here we have changed the file to use our animal.proxy.ts file to get an instance of our animal from the appContainer, which we configured earlier to be a cat.

If you re run these commands, you should still see a stunning “Meow!”:



gulp scripts
npm start

Albeit a less tightly coupled meow, that is.

That’s about it, feel free to play around with the project and see if you can get it to work with a different animal, just the same way we did with the cat, there is an example of this in this branch of the project with a dog 🙂

I’ve been using InversifyJs a lot lately and its really powerful stuff, especially when it comes down to configuration and differing implementations of an interface in a solution.

 

Leave a Reply

Your email address will not be published. Required fields are marked *