Home GitMarker - (DI) Injecting Types using InversifyJS
Post
Cancel

GitMarker - (DI) Injecting Types using InversifyJS


Why using Dependency Injection?


One of my concerns, when I start any project is keeping the code highly maintainable. That can be achieved in many ways, and sometimes it can be subjective from person to person, but over the years, some patterns and techniques were well established when it comes to object-oriented programming, and since TypeScript was the chosen language for building GitMarker, I wanted to make it as SOLID as possible.

The idea of using the Dependency Inversion Principle (DIP) occurred naturally to achieve loose coupling between the types I needed to create. You can do it relying on abstractions rather than concretions. If you want to see SOLID in practice, please check my project where I showcase all the 5 principles. I also wrote an article split into two parts explaining how the project fits with the principles. check it out!

The goal here is not to give you a course of Inversion of Control (IoC) or Dependency Injection (DI), but briefly, Inversion of Control is a design principle, while Dependency injection a design pattern and a subset of IoC. There are plenty of frameworks for managing dependencies automatically, and they’re known as IoC containers.

Fundamentally, IoC containers must provide ways to register, resolve and dispose of types. I searched for widely used IoC containers and could find excellent options, such as:

I’ve chosen InversifyJS for its popularity.

Installing InversifyJS

1
$ npm install inversify reflect-metadata --save

As mentioned on the official documentation, you will need to enable these keys in the tsconfig.json.

{
  "compilerOptions": {
     "experimentalDecorators": true,
     "emitDecoratorMetadata": true

Hello World, Command!


Although TypeScript is a superset of JavaScript, in a sense, all valid JavaScript is a valid TypeScript code; implementing types, as the name suggests, is the sole purpose the language was for, so I resisted the temptation of keeping exported functions for the commands. Even if I put them as separate and well-organized files, I wanted to stick with the language paradigm.

However, when you bootstrap an extension, you may notice that the only .ts file you got in addition to the test suite files, is the extension.ts, which exports the activate function, and then, the first command was registered to the context. There’s also a required reference of this command in the package.json manifest file, which I will cover in a further chapter.

export function activate(context: vscode.ExtensionContext) {

  let disposable = vscode.commands.registerCommand('gitmarker.createCategory', () => {
      vscode.window.showInformationMessage('Hello World from gitmarker!');
  });

  context.subscriptions.push(disposable);
}

Making it classy!


Let’s convert this into a class, and to do the same for all commands, I created a common interface named Command defining the command’s Id:string and the execute () function. Consequently, a class implements the interface.

The keyword get for Id defines a getter, so you can refer to Id by calling CreateCategory.Id, is pretty similar to C# and other languages. Also notice the @injectable() decorator, imported from 'inversify' module. It will make sense soon.

Last is creating and exporting the TYPES object to define some identifiers to Inversify. Since all of them implement the Command interface, you only need the base type for commands.

let TYPES = {
    command: Symbol("Command")
};

export default TYPES;

Containing things


As suggested from the framework’s documentation, I created the inversify.config.ts file to instantiate and export a container and bind all my @injectable types


Once I had it all set, I needed to replace the code from extension.js with an automatic command register; otherwise, I would have to register all the commands to the context manually. To ease that, I implemented the service named CommandsManager. I based this code on this very interesting article explaining some techniques of DI with VS Code extensions, which I used on GitMarker. Thanks to that, I could improve my code a lot.


Finally, the activate() method is now simple and clean. Notice that you can call any registered type from a container as below.

container.get<ClassName>(TYPES.registeredType);

I hope it can inspire you as other blogs have inspired me to seek elegant and optimal solutions like this. See you in the next chapter!





This post is licensed under CC BY 4.0 by the author.