Pigly: Unobtrusive Dependency Injection for Typescript
Many years ago I invited my friend to help with a C# project I was working on — within two days he had completely refactored my code so that it used Dependency Injection (DI). It took a while for me to accept (not cry about) what he had done. However, once I had the light-bulb moment, as to why Inversion-Of-Control makes life so much easier, I could never go back.
So I had many happy years employing DI in fairly large, modular applications. Then, three years ago, I made the case at my work that we should migrate to a Typescript/Javascript application stack. I very quickly realised DI containers like NInject are simply not possible in Javascript, and that was a problem.
So what is the problem? Well, I want…
kernel.bind<IFoo>( /*... to something*/ )let foo = kernel.get<IFoo>();
As anyone who has spent a few hours with Typescript will know, the JS run-time has no clue about your Typescript interfaces and that has made making a DI for Typescript rather difficult. Most solutions for this problem use Decorators on the class, or within constructors, to “add” the reflection information. This gets tricky when you want to provide dependencies to third-party classes out of your control. Worst it hard-couples your Classes to the DI’s decorators.
A few years ago the Typescript team exposed their transformation pipeline on the Typescript compiler. Transformers allow you to alter the code just before it it is emitted to Javascript. I knew at the time that this could help in making a true DI system. Alas, editing Abstract Syntax Trees (AST) is scary…

It wasn’t until I found someone who had done the proof of concept that it really became self-evident it could work and I put in some effort to accomplish my own DI system. Enter Pigly:

Example: Setup
Install the dependencies:
yarn add pigly
yarn add @pigly/transformer -DTo run the transformer we’re going need to use ttypescript and ts-node, so install those too:
yarn add ts-node -D
yarn add ttypescript -D
yarn add typescript -Dcreate a tsconfig.json:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"plugins": [{
"transform": "@pigly/transformer"
}]
}
}create example.ts:
import { Kernel, toSelf, to } from ‘pigly’;interface INinja {
weapon: IWeapon;
}interface IWeapon {
name: string;
}class Ninja implements INinja {
constructor(public weapon: IWeapon) { }
}class Axe implements IWeapon {
name = “axe”;
}let kernel = new Kernel();kernel.bind(toSelf(Ninja));
kernel.bind(toSelf(Axe));kernel.bind<INinja>(to<Ninja>());
kernel.bind<IWeapon>(to<Axe>());let ninja = kernel.get<INinja>();console.log(ninja);
Example: Bindings
So lets break down the important parts of the example.
class Ninja implements INinja {
constructor(public weapon: IWeapon) { }
}Here we have an INinja implementation that has a constructor dependency of IWeapon. To provide that dependency we build a DI container and configure it to bind types to providers.
let kernel = new Kernel();kernel.bind(toSelf(Ninja));
kernel.bind(toSelf(Axe));kernel.bind<INinja>(to<Ninja>());
kernel.bind<IWeapon>(to<Axe>());
… where toSelf<Ninja>()is a special (transformer-only) provider that is used to infer the constructor requirements and where to<Ninja>()is used to redirect service requests for INinjato Ninja.
Finally we ask the kernel to resolve the type to an actual value…
let ninja = kernel.get<INinja>();Example: Run it
So with the above files saved, and the node dependencies installed, we can run the code with …
ts-node --compiler ttypescript example.ts…and (drum roll please):

… the DI system was properly configured, the class dependencies were inferred, and ultimately we got a Ninja instance with an injected Axe instance.
If you’ve ever used a DI system in C# you’ll hopefully appreciate why I’m pretty happy this works. Binding to interfaces directly means you can refactor them to your heart’s content and nothing will break. It means you don’t have to adulterate classes just to make them work with the DI. Just bind A to B and off you go…
Conclusion
Pigly is a compile-time and run-time toolkit that aims to deliver true separation of concerns and help you build a DI container linked directly to interface and class types.
Development is still on-going. You can track the progress, as well as view the documentation, at https://github.com/pigly-di/pigly