Simplifying Dependency Injection and IoC Concepts using TypeScript
It is never easy to do everything by yourself. Since the beginning of time, humans truly understood, often with a huge cost, that their true power lies not in conflicts but in collaboration. Programming paradigm is also not quite different. For an application to live long, it must figure out the dependencies and try to look for ways to delegate others to serve the dependencies it needs. Not only this can solve its own problem efficiently, but it also, in turn, help other programs by presenting itself as their dependency. Dependency Injection is a crucial application design pattern for almost all the frameworks out there to create reusable, manageable and testable code. Today let’s try to simplify Dependency Injection, which is a subset of Inversion of Control principle, with TypeScript.
But first, let us set the table straight. If you have noticed any top class restaurant’s kitchen operation then you will find this article’s motivation a bit similar. In a restaurant, each Station Chef is responsible for running a specific section of the kitchen and they are being managed directly by the Head Chef or by the second-in-command Sous Chef. Station chefs can be in charge of different things respectively. A list of station chefs includes but is not limited to butcher chef, fish chef, grill chef, pantry chef, etc.
Whenever one order comes to the kitchen via a Caller, the Sous Chef simply doesn’t start preparing it all by himself. He runs down what are the things he needs to deliver, and start instructing them accordingly to each Station Chef. He expects they would handover their prepared items on a common table, where the entire dish can then be prepared or garnished for serving. This is an important takeaway that we will soon find out in this article but before that here is a rundown of the topics you will get introduced in this article.
- Dependency Injection - Dependency Inversion Principle - Inversion of Control - Inversion of Control Container - TypeScript Interfaces - Decorator Functions - Reflection APIs - TypeScript IoC
Let’s present a problem we are going to recreate and solve, called “Pizza making chronicles”.
Pizza making got several dependencies to start with. Ignoring cheese and toppings (even sauce) for simplicity, we can see that pizza needs dough and dough needs flour and yeast. Flour and Yeast need water. Water needs salt. Here is a directed graph diagram that roughly shows these dependencies:
The blue line shows the dependencies of dough.
Pizza needs dough to start with. Let’s see how does that look in a typical code base.
Soon we figure out that this
Doughclass is also required for making bread, so its redundant to allow both
Bread to be in charge of
Dough all by themselves. So let’s delegate the creation of
Dough to someone else. We inject the dependency
Pizzaconstructor, an example of the most famous constructor based Dependency Injection.
However, we passed a concrete implementation of
Dough in Pizza, called
DoughEntity. But customers might order different types of dough for different pizzas later.
Dependency Inversion Principle
Since dependency implementations can be swapped easily as they are injected during runtime, we rather inject an interface, like
IDoughso that we can later swap it with any concrete implementation of
You can read more about the benefits of having interface defining an action and other classes implementing the interface in another article of mine:
With dependency injections, we ensured that the chef making pizza can’t make its own dough, and with dependency inversion principle we ensured that different kinds of dough can now be used for making pizza. Same goes for dough maker. Dough maker doesn’t decide which flour and what kind of flour it will use rather it is handed to him by someone else in the charge of the pantry.
There is a slight problem with this pattern in applications. This might lead to a nested and complicated dependency graph because we keep manually initiating the dependencies and pass them up to the ones who need it. So what to do?
Inversion of Control
In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework.
Dependency Injection so far looked like this: The Caller needs a pizza and announces it in the kitchen. Pizza Chef doesn’t make the dough himself, he asks it from the dough maker.
But imagine the dough maker now says, “Don’t ask me for the dough, I will keep my dough on a table when I am done myself.”
There is a Hollywood Principle, which states:
“Don’t Call Us, We’ll Call You”
This is what inversion of control looks like. The Pizza Chef doesn’t directly gets the dough from the dough maker, rather he will get if from an external place without him having a knowledge of who or when it was exactly kept there.
Inversion of Control Container
The basic idea is that on startup of your application, you can define mappings between your abstractions and your implementations — eg. between your interfaces and concrete types. Then the IoC Container library will handle creating your objects for you and will automatically inject any dependencies.
So finally we have a kitchen table which contains the prepared items. Pizza chef takes everything from there when he needs it and starts making pizza. A programming container also behaves as such, which typically bind interfaces to implementations and serves them as dependencies when someone needs it. It will look something like this in the code.
Now let’s code the talk using TypeScript. The demo code is a NodeJS application build using Express framework. We will be using InversifyJS, a IoC container for TypeScript, in this project. Here are the dependencies used:
I used lightweight InversifyJS to simply demonstrate how containers work in applications, and then it will be easier for us to understand how frameworks like Angular or Laravel uses DI and IoCs. You can check out the entire repository here:
Let us create an interface called
Dough. I don’t like using
IDoughnaming, so would name dough as
for the rest of the code.
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form@expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
These decorator functions basically add metadata to our classes. The metadata are used to gain information about what dependencies it needs during runtime and thus helps in resolving them.
In Inversify, whenever a class (it will always have
@injectabledecorators on them) have a dependency on an interface, we use the
@injectdecorator to define an identifier for the interface that will be available at runtime.
For the class
DoughEntitydependency is called, the Injectable Decorator adds a metadata entry for the property using the
Reflect.metadatafunction from the
reflect-metadatalibrary, which gives us a array of dependencies that the class requires. It under the hood presents us with the right information about
DoughEntity being a dependency for
Here is a simplified example of the metadata generated when we pass a concrete implementation. You can find this behaviour in NodeJS frameworks like NestJS, and its decorator usage .
We can see that the metadata points to exact
DoughEntity. But what if we injected
Object, with no way for us to say which specific object it is. We need to do something to uniquely identify them so that during runtime the proper class is resolved.
In our demo code after having coded in the interfaces, we type-hint our real classes instead of interfaces. We use Symbol to allow identification of
Dough interface with
Let’s create a concrete implementation of
Dough now and call it
DoughEntity. Here you can see, we mentioned the class as injectable using
@injectable decorator, and passed in interfaces in the constructor with
@inject decorator so to define the identifiers.
Now we create a container that binds the interfaces to their implementations such that if
Dough types are called, we get concrete implementation of
We create the
Pizza class as following:
tsconfig.json, we could see that the
Object we get in metadata will be of type
dough.entity.tswould look like this, where param orders tell us about
It surely seems that the decorators and reflection API have done its part in figuring out the dependency.
And now the moment of truth, let’s resolve a dependency here in the
main.tsfile and see it in action.
If we run the application and open the console, we can see the following output telling us the Dependency Injection worked using the IoC container.
Saltgets initiated first.
Doughclasses were successfully resolved and finally got injected into the
There is a reason why good kitchens operate similar to this. A real chef can vouch about it more than me for sure. They can truly deliver the best when each of the chefs has singular, separate responsibilities, each knowing exactly what, where and when to contribute in making a perfect dish for the restaurant-goers, with someone managing it, who is however not directly being involved in any of the process details.
I am really impressed with the DIs and IoCs of Laravel, Angular and NestJS. It has really made both the backend and frontend code much more manageable, reusable and testable with time. The key concepts are the same in all the frameworks and are rightly so. Feel free to reach out to me on Twitter (@saadbinamjad), and you can check some other articles posted by me in our Engineering Blog. If you want to read more on some diversified topics, please check out our company blog.
Thanks, till then happy coding!