Dependency injection in TypeScript

29,144

Solution 1

I have developed an IoC container called InversifyJS with advanced dependency injection features like contextual bindings.

You need to follow 3 basic steps to use it:

1. Add annotations

The annotation API is based on Angular 2.0:

import { injectable, inject } from "inversify";

@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(); };

}

2. Declare bindings

The binding API is based on Ninject:

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;

3. Resolve dependencies

The resolution API is based on Ninject:

import kernel = from "./inversify.config";

var ninja = kernel.get<INinja>("INinja");

expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true

The latest release (2.0.0) supports many use cases:

  • Kernel modules
  • Kernel middleware
  • Use classes, string literals or Symbols as dependency identifiers
  • Injection of constant values
  • Injection of class constructors
  • Injection of factories
  • Auto factory
  • Injection of providers (async factory)
  • Activation handlers (used to inject proxies)
  • Multi injections
  • Tagged bindings
  • Custom tag decorators
  • Named bindings
  • Contextual bindings
  • Friendly exceptions (e.g. Circular dependencies)

You can learn more about it at https://github.com/inversify/InversifyJS

Solution 2

I use infuse.js for Dependency Injection in TypeScript.

Reference the d.ts

/// <reference path="definition/infusejs/infusejs.d.ts"/>

Initialize your injector at startup

this.injector = new infuse.Injector();  

Map Dependencies

this.injector.mapClass( 'TodoController', TodoController );
this.injector.mapClass( 'TodoView', TodoView );
this.injector.mapClass( 'TodoModel', TodoModel, true );  // 'true' Map as singleton

Inject Dependencies

export class TodoController
{
    static inject = ['TodoView', 'TodoModel'];

    constructor( todoView:TodoView, todoModel:TodoModel )
    {

    }
 }

It's string based as opposed to being type based (as reflection isn't yet possible in TypeScript). Despite that, it works very well in my applications.

Solution 3

Try this Dependency Injector (Typejector)

GitHub Typejector

With new TypeScript 1.5 it is possible using annotation way

For example

    @injection
    class SingletonClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }
    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(SingletonClass)
        public helper: SingletonClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }
    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

For question case: some property should realise pseudo Interface (or abstract class) like in next example.

    class InterfaceClass {
        public cat: string;
        public dog: string;

        public say() {

        }
    }

    @injection(true, InterfaceClass)
    class SingletonClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`${this.cat}-Cat and ${this.dog}-Dog`);
        }
    }

    @injection(true, InterfaceClass)
    class MockInterfaceClass extends InterfaceClass {
        public cat: string = "Kitty";
        public dog: string = "Hot";

        public say() {
            alert(`Mock-${this.cat}-Cat and Mock-${this.dog}-Dog`);
        }
    }

    @injection
    class SimpleClass {
        public say(something: string) {
            alert(`You said ${something}?`);
        }
    }

    @resolve
    class NeedInjectionsClass {
        @inject(InterfaceClass)
        public helper: InterfaceClass;
        @inject(SimpleClass)
        public simpleHelper: SimpleClass;

        constructor() {
            this.helper.say();
            this.simpleHelper.say("wow");
        }
    }

    class ChildClass extends NeedInjectionsClass {

    }

    var needInjection = new ChildClass();

Note: Mock injection should define after source code, because it mast redefine class-creator for interface

Solution 4

You can use the solution:
Lightweight dependency injection container for JavaScript/TypeScript

import {autoInjectable, container} from "tsyringe";

class MyService {
  move(){
    console.log('myService move 123', );
  }
}

class MyServiceMock {
  move(){
    console.log('mock myService move 777', );
  }
}

@autoInjectable()
export class ClassA {
  constructor(public service?: MyService) {
  }
  move(){
    this.service?.move();
  }
}

container.register(MyService, {
  useClass: MyServiceMock
});

new ClassA().move();

output:

mock myService move 777

Solution 5

For people who use Angular2 I have developed Fluency Injection https://www.npmjs.com/package/fluency-injection. The documentation is quite complete and it mimics the behaviour of Angular2's DI.

Feedback is much appreciated and I hope it helps you :)

Share:
29,144
Sop Killen
Author by

Sop Killen

Humble programmer that reached my zenith in 2011 :) 3619 - Sum of Different Primes

Updated on February 03, 2020

Comments

  • Sop Killen
    Sop Killen over 4 years

    I'm looking into the possibilities to do TDD with TypeScript. If I write my tests in TypeScript, is it possible to make the import statements return mocks for my class under test? Or is the only feasible approach to write the tests in pure javascript and deal with injecting AMDs myself?