Minimalistic Dependency Injection (DI) container in TypeScript

In this blog post, I will try to explain a possible way how to implement a dependency injection in TypeScript. An injection means, you don’t need to call new MyClass() because classes are managed and instantiated by a DI container automatically. I would like to achieve the following minimalistic goals:

  • Use constructor based injection to inject class instances into each other.
  • For the sake of convenience, the injected classes exist as singletons inside a DI container. The class instances live inside the DI container.
  • Possibility to define an entry point (entry class) to bootstrap the DI container. You can bootstrap as many containers as you want.
  • Possibility to release the DI container by calling a “release” function.

We will start with a concrete example. Imagine a libray named “Rendering Engine”. There is a main class RenderingEngine and many “manager” classes which we want to inject into each other via constructor. Something like this:

@InjectableClass() is a custom TypeScript Decorator which I wrote to allow the constructor based dependency injection. The implementation is trivial:

We also need a “Releasable” interface to be able to release class instances when we shutdown the DI container.

Now, we need “reflect-metadata” — an implementation of Metadata Reflection API for ECMAScript. This is a key concept for the developed approach. If you let TypeScript emit the metadata by setting proper options in tsconfig.ts

you can use runtime reflection on types. That means, it is possible to find out the types of injected classes at runtime by calling

Reflect.getMetadata('design:paramtypes', target) 

The target is some class type. If we know the class types, we can instantiate those classes. Let’s create an “Injector” class with a “resolve” method which gets the class type as parameter. The injector implementation extends a map (class Map). The map acts as DI container. If a class is not yet available in the map, it will be created and saved the map. Otherwise, an existing class instance is taken from the map. The “resolve” method returns the class instance.

A “bootstrap” function creates all classes in the DI container by the injector automatically. We have to pass the type of the “entry point” class (e.g. RenderingEngine from the example above). The return value is an array with the instantiated “entry point” class and a method which invokes all release methods on the classes living in the DI container.

Last bot not least — the unit tests which demonstrate how to use this implementation in TypeScript.

A complete project is available on GitHub: https://github.com/ova2/frontend-tooling-tutorial/tree/master/typescript-playground/dependency-injection-container

Have fun!