Typescript dependency injection: setting up InversifyJS IoC for a TS project
Rather than dealing with the manual creation of class dependencies each time we want to utilize a particular class. We could set up a mechanism which could create them for us and automatically provide the dependencies to the class. Such a mechanism is called an Inversion of Control (IoC) container and in this post, I would like to show how can you improve your TypeScript code by setting up a dependency container using InversifyJS.
Project setup before the use of the dependency injection (DI)
I’m going to show you an example based on a node demo project which consists of a service class depending on two other classes, and the main TypeScript file which is utilizing this service. The structure of the project is like
src/
dependencies.ts
service.ts
main.ts
And you can also see it on GitHub https://github.com/AndrejsAbrickis/ts-inversify-blog
dependencies.ts
This TypeScript file exports two classes which behave the same. Both of them has a private field name
representing the name of the class. And both have private method getName()
which returns the name of the class.
service.ts
All that the service class is doing is returning an array of the dependencies names it is using. As we cannot directly access the read-only name field on the classes, we have to use the instance method getName
. And to use the method, we must instantiate the classes using the new
keyword
main.ts
When executed, the main file is ‘new-up-ing’ (manually creating a new instance of a class) the service class, calling it’s getAllNames
method and logging the result of the method
As you saw, during the development we have used the new
keyword for a few times. And this is just a trivial example of a service and two dependencies. Imagine doing this in a real-life project with tens and hundreds of classes.
Add inversify to the project
To implement DI in the project I’m going to use InversifyJS as the IoC (inversion of the control) container.
First, we need to add inversify
and reflect-metadata
to the project
yarn add -D inversify reflect-metadata
Second: update the tsconfig.json
by adding extra settings to compilerOptions
section
Project setup after the use of the dependency injection (DI)
After the InversifyJS is installed and TypeScript compiler is configured to support InversifyJS, we can update our application’s code.
di-container.ts
Before to enjoy the sweet fruits of dependency injection, we have to configure the IoC container, so that the classes can resolve their own dependencies from the centralized container.
We do this by creating a new inversify Container and providing it with the bindings of the classes. The bindings allow the container to map requested dependency with an instance of it.
In this example I’m using the toSelf()
binding for classes DependencyA
and DependencyB
. Which instructs the container to return an instance of class whenever a particular class is requested (injected).
dependencies.ts
After the container is set up the dependencies can be made injectable by importing injectable
decorator from inversify and decorating classes with @injectable
decorators. This decorator will be handled and applied to the JavaScript output using reflect-metadata
package during TypeScript compilation.
service.ts
Now the new
keyword can be removed from the service class. And the dependencies can be injected straight into the class’ constructor by using @inject
decorator.
The following example will retrieve the instance of DependencyA
class from the IoC container and pass it to constructor as parameter dependencyA
@inject(DependencyA) dependencyA: DependencyA
main.ts
First update the import by addin import 'reflect-metadata';
. It's required by inversify to apply @inject()
and @injectable
decorators to the compiled output of the application.
After that, we can import the container for the application, and the new
keyword can be removed from the main.ts file by changing the service
declaration to
const service: Service = DIContainer.resolve<Service>(Service);
Now we’re no longer required to manually create Service
class dependencies as the Container will do it for us whenever it will be asked to resolve the Service
class.
Conclusion
Dependency injection is a pattern which removes the responsibility of manually creating class dependencies each time we want to use a particular class. Instead, we configure the Inversion of Control (IoC) container to do this for us.
The main benefits I see in this pattern is that we can mock and substitute the concrete instance of dependency. Thus we can make it easier to write tests for our class behavior without the need to manually create all the dependencies. And by utilizing interfaces and IoC container we can make our code to be more extensible.
You can see both of the implementations befora DI
and after DI
on Github https://github.com/AndrejsAbrickis/ts-inversify-blog. To see the compiled TypeScript, clone the repo, run
yarn && yarn build
or
npm install && npm build
and see the ./dist
directory for the output.
Cheers!
If you found this post useful and would like to read more about random web development topics, just clap for this article or drop a comment here. And as always you can find me on Twitter@andrejsabrickis
Before I go just a quick word that we are hiring. Currently, our company is looking for creative and open-minded Software Engineers to help us grow the leading peer-to-peer investment marketplace. If you are interested or you know someone who would be interested, don’t hesitate to contact me personally on Twitter @andrejsabrickis. Also, check out our current job opportunities at Mintos.
Cheers!
/Andrejs/