Dependency Injection for TypeScript
Tags: typescript, di
Published: 2021-09-20 07:22:41

## Ref
- [Introduction to Clean Architecture for TypeScript: PART1](https://dev.to/aidy1991/introduction-to-clean-architecture-for-frontend-engineer-part1-42mi)
- [Introduction to Clean Architecture for TypeScript: PART2](https://dev.to/aidy1991/introduction-to-clean-architecture-for-typescript-part2-11pi)
- [Introduction to Clean Architecture for TypeScript: PART3](https://dev.to/aidy1991/introduction-to-clean-architecture-for-typescript-part3-aei)
- [Introduction to Clean Architecture for TypeScript: PART4](https://dev.to/aidy1991/introduction-to-clean-architecture-for-typescript-part4-1h5f)

## Abstract

Clean Architecture deals with concentric architecture with the business domain at the core, but
One of the key principles is the dependency constraint, which states that we should only depend on the inside from the outside.
However, in a straightforward implementation, the processing flow and dependencies would be in the same direction.
Of course, the Clean Architecture also requires processing from the inside to the outside, so a straightforward implementation will result in dependencies from the inside to the outside.
In other words, in order to observe the dependency principle, the flow of processing and the direction of dependency must be reversed.
The technique for solving this problem is called dependency inversion.
As the name suggests, this technique is used to satisfy the Dependency Inversion Principle (DIP).
So how do we reverse the dependency?
In this chapter, we introduce a technique called Dependency Injection (DI), which is commonly used to reverse dependencies.

## DI library for TypeScript
Dependency Injection is a technique that has been used for a long time in statically typed languages such as Java.
You can build your own mechanism to realize DI, or you can use existing DI libraries (DI frameworks).
For example, in Java, there are many DI libraries such as Spring Framework and google/guice.
These DI libraries allow you to use DI for developing various applications.

How about in the context of JavaScript and TypeScript?
It may not be common to use DI in web frontend development.
Nevertheless, there are DI libraries available for JavaScript and TypeScript.
For example, AngularJS and Angular have a DI framework built in.
There are also libraries that provide DI functionality on its own, such as [InversifyJS](https://github.com/inversify/InversifyJS) and [TSyringe](https://github.com/microsoft/tsyringe).
Both InversifyJS and TSyringe are DI libraries for use with TypeScript.
InversifyJS is a library with over 5000 Github stars and is used in over 18,000 repositories.
On the other hand, there doesn't seem to be much active development going on these days.
TSyringe is a DI library that is mainly developed by Microsoft. As of September 2020, it has about 1,400 stars on Github, and it seems to be under continuous development.

In this chapter, we will first introduce a simple method to perform DI without using libraries.
This simplified method uses a simple example to show what DI is trying to accomplish and what problems it has.
We will then explain how TSyringe can be used to solve these problems.

## Simple DI without libraries
In this section, we will use a simple example to illustrate DI without using libraries.
The goal is to demonstrate an understanding of the overview of DI and the issues involved in DI.
However, as will be explained later, there are some practical problems with the method presented in this section.
In the next section, we will confirm that these problems are solved by DI using the library.

In this section, we will deal with a simple subject as follows.

```typescript
import Printer from './Printer';

class Document {
  constructor(private content: string) {}

  output() {
    const printer = new Printer();
    printer.print(this.content);
  }
}
```

```typescript
export default class Printer {
  constructor() {}

  print(content: string) {
    console.log('Print:', content);
  }
}
```

`Document` will output its own content using the `output` method.
In the code above, the `output` method uses the class `Printer` internally.
`Printer` prints out the received string by the `print` method.
However, in order to simplify the implementation, we only use `console.log` for the output.
The codes to use `Document` are as follows.

```typescript
import Document from './Document';

const document = new Document('this is sample text');
document.output() // "Print: this is sample text"
```

Looking at the dependencies, we see a dependency from `Document` to `Printer`.
Now let's assume that `Email` is added as an output of `Document`.
An example codes for this would look like the following

```typescript
import Email from './Email';
import Printer from './Printer';

export default class Document {
  constructor(private content: string, private method: Printer | Email) {}

  output() {
    if (this.method instanceof Printer) {
      this.method.print(this.content);
    } else {
      this.method.send(this.content);
    }
  }
}
```

```typescript
export default class Email {
  constructor() {}

  send(content: string) {
    console.log('Email:', content);
  }
}
```

New `Email` has been added, and `Document` now receives the output method in the constructor.
The dependency in this implementation is still the same as before, from `Document` to `Printer`.
In addition, `Document` now also depends on `Email`.
The code for the user side looks like this.

```typescript
import Document from './Document';
import Printer from './Printer';
import Email from './Email';

const printerDocument = new Document('this is sample text', new Printer());
printerDocument.output(); // "Print: this is sample text"

const emailDocument = new Document('this is sample text', new Email());
emailDocument.output(); // "Email: this is sample text"
```

As you may have already noticed, this implementation violates the DIP.
This is because `Document` depends on the concrete implementations `Printer` and `Email`.
This means that if `Printer` or `Email` is changed, `Document` will also be affected.
In a simple example, if you rename the `print` method of `Printer`, you will need to modify the `output` method of `Document` too.
Also, if you want to add more output methods for `Document`, as you did for `Email`, you need to modify `Document`.
This is a violation of the Open-Closed Principle (OCP): "open to extensions".
To solve the above problems, we will use DI to reverse the dependencies.
Reverse the dependency in the following steps.

 1. Create `Document` interface on `DocumentOutputMethod` side
 2. `Printer` and `Email` implement the `DocumentOutputMethod` interface
 3. `Document` implements the process using the interface
 4. Pass an instance of the class that implements the `DocumentOutputMethod` interface to the constructor of `Document` in the user side codes

This is called Dependency Injection (DI) because the instance to be depended on in step 4 is injected from the outside.
Let's take a look at an example implementation.

```typescript
export interface DocumentOutputMethod {
  output: (content: string) => void;
}

export default class Document {
  constructor(private content: string, private method: DocumentOutputMethod) {}

  output() {
    this.method.output(this.content);
  }
}
```

Added the `DocumentOutputMethod` interface under the management of `Document`.
And `Document` uses this `DocumentOutputMethod` to implement the `output` method.
By doing this, you can see that it no longer depends on specific implementations such as `Printer` or `Email`.

```typescript
import { DocumentOutputMethod } from './Document';

export default class Printer implements DocumentOutputMethod {
  constructor() {}

  print(content: string) {
    console.log('Print:', content);
  }

  output(content: string) {
    this.print(content);
  }
}
```

```typescript
import { DocumentOutputMethod } from './Document';

export default class Email implements DocumentOutputMethod {
  constructor() {}

  send(content: string) {
    console.log('Email:', content);
  }

  output(content: string) {
    this.send(content);
  }
}
```

`Printer` and `Email` implement the `DocumentOutputMethod` interface.
Added `output` method to satisfy the `DocumentOutputMethod` interface.
This means that `Printer` and `Email` now depend on `Document`.
The above fix reversed the dependency.

```typescript
import Document from './Document';
import Printer from './Printer';
import Email from './Email';

const printerDocument = new Document(
  'this is sample text',
  new Printer()
);
printerDocument.output();

const emailDocument = new Document('this is sample text', new Email());
emailDocument.output()
```

Finally, in the code of the user side, the constructor of `Document` is passed an instance of a class that implements the `DocumentOutputMethod` interface.
Now you can do DI without using the library.

### Problems with this method

Actually, there is a problem with this method.
That is, when you instantiate `Document`, you need to instantiate and pass in `Printer` and `Email`.
This is another way of saying that any code that uses `Document` will always depend on `Printer` and `Email`.
In the above example, `main.ts` is affected by this, but
If you use `Document` in multiple locations, each location will depend on `Printer` and `Email`.
After removing the dependency on the concrete implementation from `Document`, this just shifts the dependency elsewhere.
To solve this problem, the dependency on concrete code should be gathered in as few places as possible, as described in [Introduction to Clean Architecture for TypeScript: PART4](https://dev.to/aidy1991/introduction-to-clean-architecture-for-typescript-part4-1h5f).

So how do we aggregate our dependency on concrete code?
This can be solved by using an object called a DI container, which has a correspondence table of dependencies.
The DI container holds a list of correspondences between the class to be injected (`Document` in this case) and the class to inject (`Printer` or `Email`).
When instantiating the class to be injected, it automatically selects the class to inject from the correspondence table of the DI container, instantiates it, and passes it on.
Then, the description of the correspondence of the DI containers is integrated into the main component or configuration files.
In this way, the class to be injected (`Document`) can be used in various places while consolidating the dependencies on concrete code.

It is of course possible to implement a DI container without any libraries.
However, there are a number of technical issues that need to be resolved, and it is quite difficult to implement them on your own.
This is a brief bullet list of issues that need to be resolved.
If you are interested, please check out the details.

 * Need to use decorator
 * Metadata needs to be retrieved by reflection
 * TypeScript interface information is removed at compile time and cannot be mapped in the DI container.

These points have been resolved in the existing DI library.
In the next section, we will show an example of a DI that solves this problem by using TSyringe.

## TSyringe
TSyringe is a DI library developed under the leadership of Microsoft.
As the name suggests, TSyringe allows you to introduce DI into your TypeScript development.

### TypeScript Decorators
TSyringe uses an experimental feature in TypeScript called Decorators.
Decorators are in the Stage 2 Draft stage of JavaScript as of March 2021.
As such, there is room for change as a specification.
Similarly, TypeScript has been implemented it as an experimental feature, and its specifications are subject to change in the future.

So why does TSyringe use Decorators?
This is because the nature of the DI library is such that it works well with decorators (as a general language feature).
The DI library needs to know in some way the class to which it is injecting dependencies.
Before decorators were used, the injection target was set by a configuration file.
In this way, it is not possible to know what the DI settings are in the file where the class is declared.
There is also the cost of writing and managing configuration files.
By using decorators, these problems can be solved.
The following is an example of using TypeScript Decorators (not related to TSyringe).

```typescript
function methodDecorator(target: any, props: string, descriptor: PropertyDescriptor) {
  console.log('This is method decorator f()');
}

function classDecorator(constructor: Function) {
  console.log('This is class decorator f()');
}

@classDecorator
class SampleClass {
    @methodDecorator
    hoge() {
        return null;
    }
}
```

In order to use Decorators in TypeScript, you need to add the following setting in the compiler options.

```typescript
  {
    "compilerOptions": {
      "experimentalDecorators": true
    }
  }
```

### Installation and initial setup
This section describes the setup for using TSyringe.
The information here is based on official documentation.
First, install the package.

Install the package `tsyringe` by

```bash
$ npm install --save tsyringe
```

or,

```bash
$ yarn add tsyringe
```


TSyringe uses an experimental feature called Decorators in TypeScript, so we will rewrite `tsconfig.js` as follows

```typescript
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
```

We can use Decorators by enabling `experimentalDecorators`.
You can also enable `emitDecoratorMetadata` to allow the decorator to handle a lot of metadata.
To enable the Reflect API to handle metadata, you need to select one of the following libraries to use as a polyfill.

 * reflect-metadata
 * core-js (core-js/es7/reflect)
 * reflection

 In this example, we install `reflect-metadata` as shown below,

```bash
$ npm install --save reflect-metadata
```

or,

```
$ yarn add reflect-metadata
```

`reflect-metadata` needs to be `import` only once before using DI.
It is a good idea to do `import` at the top level as much as possible.

```typescript
import "reflect-metadata";
```

Now you are ready to use TSyringe.
Next, let's look at how to actually do DI.

### Define the class where you want to do DI
TSyringe provides a simple API for doing DI.
The implementation code differs depending on whether `interface` is used for the type of the object to be injected or not.
Here is an example of how to use `interface`.
For more information, please refer to the official documentation.

First, define the type to be injected with `interface`.

```typescript
// apiInterface.ts
export interface User {
  id: number;
  name: string;
}

export interface UserApiInterface {
  fetchUser: (args: { id: number }) => User;
}
```

`UserApiInterface` defines an API for retrieving user information.
It is assumed that the class that implements this interface includes processes such as communicating with the API server and connecting directly to the DB.
In the following, as a mock, we will implement a class that receives a user ID and returns static information.

```typescript
// apiImpl.ts
import { UserApiInterface, User } from './apiInterface';

export class UserApiImpl implements UserApiInterface {
  fetchUser({ id }: { id: number }): User {
    return {
      id,
      name: `This is fetchUse of UserApi: ${id}`.
    };
  }
}
```

Then, define the class in which `UserApiInterface` should be injected.

```typescript
// api.ts
import { injectable, inject } from 'tsyringe';

import { UserApiInterface } from './apiInterface';

@injectable()
export class UserApi {
  constructor(
    @inject('UserApiInterface') private api: UserApiInterface
  ) {}

  fetchUser(args: { id: number }) {
    return this.api.fetchUser(args);
  }
}
```

`UserApi` receives an object of type `UserApiInterface` in `constructor` as an argument and keeps it as a private member.
Then, in `UserApi.fetchUser()`, it calls `api.fetchUser()`, which is implemented in `api`.
In this way, `UserApi` can change its behavior depending on the object passed in `constructor`.
By writing `@inject('UserApiInterface')` in `constructor`, we pass the interface metadata to the DI library side.

### Injecting an object

Now, inject the object into `UserApi`.

```typescript
// main.ts
import { container } from 'tsyringe';

import { UseApiImpl } from './apiImpl';
import { UserApi } from './api';

container.register('UserApiInterface', {
  useClass: UserApiImpl
});

const userApi = container.resolve(UserApi);
```

`container` is TSyringe's DI container, which holds the information to perform DI.
This `container`'s `register()` method sets the object to be injected.
In this example, we are configuring `UserApiInterface` to inject `UserApiImpl`.
And finally, `container.resolve(UseApi)` instantiates `UserApi`.
At this time, `container` instantiates `UserApiImpl` and passes it to `UserApi`.

In other words, TSyringe is a mechanism to create instances while resolving dependencies (which registered by `register()`) by `resolve()`.

This is the basic usage of TSyringe.
This DI mechanism can be used to reverse the dependency.

## Dependency Inversion with DI Library
In the previous section, we presented an example of DI using TSyringe.
As many readers may have noticed from the implementation examples, using a DI library like TSyringe does not automatically reverse the dependencies.
The DI library does this by externally selecting and injecting the objects that a class depends on.
Therefore, it is possible to reverse the dependency by using it properly, but on the other hand, it is not possible to change the direction of the dependency if it is not used properly.
This section describes how to use the DI library to reverse the dependencies.

### Design modules

![user-database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dp0hbcgd8jqwwb2ih700.png)

First, let's design modules that will be the subject of our example.
In this section, we will consider the `User` model, which is the domain model, and `Database`, which is responsible for persisting the model.
`User` corresponds to the business logic and is located at the center of the concentric circles diagram.
And since `Dataset` is a concrete module, it is located outside the concentric diagram.
Therefore, in accordance with Clean Architecture, the dependency goes from `Database` to `User`.
This is represented in the diagram as the above image.
`Database` can know about `User`, but conversely, `User` cannot know about `Database`.
For the sake of illustration, let's assume that one file corresponds to one module, and create `user.ts` and `database.ts`.

First, let's define each one without considering the correct dependencies.

```typescript
import Database from './database';

export default class User {
  private _id: number | null;

  private _name: string;

  constructor(private database: Database) {
    this._id = null;
    this._name = '';
  }

  set id(value: number) {
    this._id = value;
  }

  set name(value: string) {
    this._name = value;
  }

  get name() {
    return this._name;
  }

  save() {
    if (typeof this._id === 'number') {
      this.database.save(this);
    }
  }
}
```

`User` can be set to `id` and `name`.
It persists itself via `Database` by `User.save()`.

```typescript
import User from './user';

export default class Database {
  save(user: User) {
    console.log(`${user.name} has been saved`);
  }
}
```

`Database` will receive the `User` model and persist it.
However, for the sake of simplicity, we will only display the received `User`'s `name` in the console.

Let's take a look at the dependency between `User` and `Database` defined so far.
`Database` is `import` the type information of `User`.
`User` also uses `Database` as `import`, but
This is because `User` uses `Database` for persistence.
If you look at these dependencies, you can see that they violate the dependency rules.

![user-database-bidirectional](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0n7p3s2n3mv81kt1k37.png)
 
Now, let's fix the dependency to the correct orientation.

### Reversing dependencies between modules
First, define `DatabaseInterface` in `user.ts`.

```typescript
export default class User {
  private _id: number | null;

  private _name: string;

  constructor(private database: DatabaseInterface) {
    this._id = null;
    this._name = '';
  }

  set id(value: number) {
    this._id = value;
  }

  set name(value: string) {
    this._name = value;
  }

  get name() {
    return this._name;
  }

  save() {
    if (typeof this._id === 'number') {
      this.database.save(this)
    }
  }
}

export interface DatabaseInterface {
  save: (user: User) => void;
}
```

Change the `constructor` argument of `User` to `DatabaseInterface`.
This will remove `import` from `user.ts` to `database.ts`.
`User` is no longer dependent on `Database`.
Next, modify `Database` so that it implements `DatabaseInterface`.

```typescript
import User, { DatabaseInterface } from './user';

export default class Database implements DatabaseInterface {
  save(user: User) {
    console.log(`${user.name} has been saved`);
  }
}
```

`Database` is importing `User`, which turns out to be fine according to the dependency rules we were aiming for.
Now we have satisfied the dependency rule.
The last step is to configure TSyringe.

```typescript
import { injectable, inject } from 'tsyringe';

@injectable()
export default class User {
  private _id: number | null = null;

  private _name: string = '';

  constructor(
    @inject('DatabaseInterface')
    private database: DatabaseInterface
  ) {}

  set id(value: number) {
    this._id = value;
  }

  set name(value: string) {
    this._name = value;
  }

  get name() {
    return this._name;
  }

  save() {
    if (typeof this._id === 'number') {
      this.database.save(this);
    }
  }
}

$export interface DatabaseInterface {
  save: (user: User) => void;
}
```

Added `User` with `@injectable()` and added `database` argument of `constructor` with the decorator `@inject('DatabaseInterface')`.
Now you can inject `Database` into `User` by `container`.
Let's take a look at the code that uses these modules.

```typescript
import { container } from 'tsyringe';
import User from './di/user';
import Database from './di/database';

container.register('DatabaseInterface', {
  useClass: Database
});

export const user = container.resolve(User);

user.id = 0;
user.name = 'test user';
user.save(); // 'test user saved' will be displayed in the console
```

This reversed the dependency and helped us to follow the rules.
The important thing here is to define `DatabaseInterface` , on which `User` originally depends, in the `User` module.
In this way, `User` can manage `DatabaseInterface` within the scope of its own responsibilities without directly relying on `Database`.
In other words, if you define `DatabaseInterface` in the `Database` module, it will lose its meaning.

Now we know that with proper use of interfaces and DI, we can reverse the dependencies between modules.

### Aggregating dependencies on concrete code
As described in the previous section, DI without libraries has a problem that it cannot aggregate dependencies on concrete code.
In the example in this section using TSyringe, we can aggregate `container.register()` into `app.ts`.
And by using `container.resolve(User)`, the code on the side using `User` no longer depends on `Database`.
In this way, we can see that the problem that the simple DI without the library has been solved.

## Ads

This article is an excerpt from the first chapter of the book "React Clean Architecture". Some wording has been changed. [You can buy the kindle version of this book from amazon](https://www.amazon.co.jp/dp/B09FG94392/).

![React Clean Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv8e3y7l0mzlvooan7c5.png)