Using Object Oriented Programming, OOP, style allows us to apply Inversion of Control, IoC, and more patterns. An IoC container helps decoupling dependencies by using a class constructor or properties to identify and inject its dependencies. This lesson will show you how can you create and use an IoC container to decouple an http client and users service dependencies in a Vue component with TypeScript and InversifyJS.
[00:00] Here we have this component, which fetches a list of users from this URL and sets them. It will show a list of them, displaying their name. The implementation is OK for an example, but for a real application, a medium large one, you would have more abstraction layers, something like this.
[00:20] Here you have a user service, and you create a new instance of it in the component. Then you call getUsers, and it returns the users. This user service is a simple class with has an instance of HTTP client, which is an abstraction of whatever HTTP client you're using. The getUsers method returns a promise by calling that URL that we had in the component before.
[00:44] The HTTP client is another class that simply has a get method, which is a wrapper over the fetch function. This still has one particular problem, which is this service has a hard dependency on HTTP client, and the app view component also has it for the user service.
[01:02] This will make somethings harder, like for example, unit testing, since we cannot easily replace the dependencies by mocked versions of it. In version-of-control containers, solve this by grouping all the dependencies into one single place.
[01:18] The create them, we are going to use Inversify. Let's write npm install inversify, and we have to install Reflect Metadata as well. Make sure that in your TS config file you have the emit decorator metadata and experimental decorators enabled. Also, include the ECMAScript 6 library.
[01:34] First, we need to import Reflect Metadata, and we have to do that only once in the whole app. The main file feels like the right place to write that.
[01:45] Then create a container file. In here, we are going to write all the dependencies, and bind them to the container. First, import container from Inversify, and then we have to create a new instance of it. Now, we can start binding all the dependencies.
[02:05] Let's import the user service from user service. Then we can user container.bind, where we write first the contract. That can be an interface for a class. Then the identifier, which can be a string, in this case, for example. Finally, the actual implementation, which in this case is going to be user service for both the contract and implementation.
[02:33] Just do the same for the HTTP client. Import it, then bind it, and export the container. Since we are going to reuse the identifiers of those bindings, we can make it more maintainable by writing them in a types file. I'll create it and write there those constants.
[03:01] Instead of a string, we can use the symbol primitive of ECMAScript 6. The symbol primitive is very useful for creating unique identifiers. Now, back to the container. We can import them. Instead of using those strings, we can use the types constant.
[03:29] Now, we have bound already those dependencies. If we go to the user service, we can remove already that dependency of HTTP, and write import inject from Inversify instead. Now, we have to go to the HTTP client, and import injectable.
[03:48] When we use it as a decorator, we are telling Inversify that that class can be injected. In user service, we import types, and remove the HTTP instance creation. We can use that inject in the constructor as an identifier to inject the dependency. This is known as cross-structure injection, and it works really well for this case.
[04:13] If we go to the app component, here we cannot use cross-structure injection, because it needs to take care of the object creation. In this case, Vue is the one who takes care of the object creation. We can use property injection.
[04:26] For that, we'll have to install inversify-inject-decorators. Then back to the container. We have to import the default function, getDecorators, from Inversify inject decorators. This will allow us to create a lazy injectable decorator for property injection. Then write const, lazy inject equal getDecorators, pass in the container to it, and we can export it.
[04:53] Don't forget in user service to add the injectable decorator to the class, otherwise Inversify doesn't know about it. In the app component, we cannot really remove the dependency to the user service and the manual instance creation. Instead, let's import the lazy inject from the container that we just wrote, as well as the types.
[05:18] Then right there in the property, we can place before the lazy inject decorator, pass in the identifier that we want to inject, in this case, user service. If we run this, we'll see that it's working right as before.