Dependency management is one of the critical points while developing applications. In the back-end world, there are many IoC container libraries that we can make use of, like Autofac, Ninject, etc. Similarly, many modern front-end frameworks also provide DI features. However, those features work way differently from how back-end libraries do. In this post, we’re going to use TypeScript and Vue.js for development and apply an IoC container library called InversifyJS that offers very similar development experiences to back-end application development.
The code samples used in this post can be found here.
provide/inject Pair in VueJs
According to the official document,
email@example.com supports DI feature using the
provide/inject pair. Here’s how DI works in VueJs. First of all, declare dependency,
MyDependency in the parent component like:
Then its child component consumes the dependency like:
Maybe someone from the back-end development got a question. Child components only consume dependencies that are declared from their parent component. In other words, in order for all components to consume all dependencies, this declaration MUST be done at the top-level component of its hierarchy. That’s the main difference between VueJs and other back-end IoC containers. There’s another question – VueJs doesn’t provide a solution for inter-dependency issue. This inter-dependency should be solved by a third-party library. But that’s fine. We’re going to use TypeScript anyway, which has a solution for the inter-dependency issue.
DI in VueJs and TypeScript
While using a class-based API by default may make it more “friendly” to devs used to classes, it also makes it more hostile to a large group of users who use Vue without build tools or transpilers. When you are advocating your preference, you might be missing some nuance we have to take into account as a framework.
This is why we offer the object-based API as the baseline and the class-based API as an opt-in. This allows us to cater to both groups of users.
Therefore, we need to sort out either using the
provide/inject pair or using another approach, ie. service locator pattern. In order to use the
provide/inject pair, as we found above, we need to put an IoC container instance at the top-level of the component. On the other hand, we can simply use the container as a service locator. Before applying either approach, let’s implement the IoC container.
Building IoC Container using InversifyJS
InversifyJS is a TypeScript library for IoC container, which is heavily influenced from Ninject. Therefore syntax is very similar to each other. Interface and class samples used here are merely modified from both libraries’ conventions – yeah, the ninja stuff!
Warrior interfaces like below:
Symbol to resolve instances. This is a sample code to define multiple symbols in one object. This object contains multiple symbols for
@injectable decorator provided by
InversifyJS defines classes that are bound into an IoC container.
The @inject decorator goes to constructor parameters. Make sure that those parameters require the
Symbol objects defined earlier.
Make sure that we should use the same
Symbol object defined earlier. If we simply use
Symbol("Weapon") here, it wouldn’t be working as each Symbol object is immutable.
Implementing IoC Container
Let’s implement the IoC container using the interfaces and models above.
The last part of the code snippet above,
container.bind(...).to(...), is very similar to how IoC container works in C#. Now we’re ready for use of this container.
Attaching Child Component
Hello.vue has got the
Ninja.vue component as its child. Let’s have a look at the Ninja.vue component.
Now, let’s apply both service locator and provide/inject pair.
Applying Service Locator
We’re updating the
Ninja.vue to use service locator:
As we can see above, the IoC container instance,
container is directly consumed within the
Ninja.vue component. When we run the application, the result might be looking like:
As some of us might uncomfortable to use the service locator pattern, now we’re applying the built-in
As we identified above, in order to consume all dependencies at all Vue components, we should declare IoC container as a dependency at the top-level of the component, ie)
We can see that the container instance is provided with the symbol,
SERVICE_IDENTIFIER.CONTAINER defined earlier. Now let’s modify the
@Inject decorator takes care of injecting the
container instance from the
App.vue component. Make sure that the same symbol,
SERVICE_IDENTIFIER.CONTAINER is used. All good! Now we can see the same result like the picture above.
So far, we’ve had an overview of how to use DI in VueJs & TypeScript app in two different approaches – service locator or
provide/inject pair. Which one to choose? It’s all up to you.