Dependency injection in TypeScript
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 :)
Sop Killen
Humble programmer that reached my zenith in 2011 :) 3619 - Sum of Different Primes
Updated on February 03, 2020Comments
-
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?