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, vue@2.2.0
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
Evan You, the creator of VueJs, has recently left a comment about his design philosophy on VueJs framework.
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!
Defining Interfaces
Let’s define Weapon
and Warrior
interfaces like below:
Defining Models
InversifyJS
uses Symbol
to resolve instances. This is a sample code to define multiple symbols in one object. This object contains multiple symbols for Warrior
, Weapon
and Container
.
The @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
Unlike the Previous Posts, We’re adding a new child Vue component, Ninja.vue to Hello.vue for dependency injection.
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 provide/inject
pair.
Applying provide/inject
Pair
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) App.vue.
We can see that the container instance is provided with the symbol, SERVICE_IDENTIFIER.CONTAINER
defined earlier. Now let’s modify the Ninja.vue
component:
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.