Passing metadata from TypeScript code to JavaScript code using decorators for Dependency Injection
Tags: typescript, angular, nestjs, ditsmod
Published: 2024-07-04 12:51:40

When TypeScript decorators are used for Dependency Injection, they are needed for two things. First, they signal to the TypeScript compiler that it needs to read TypeScript types, transform them into JavaScript code, and save them. Second, if additional metadata is passed to the decorator factory, it must also be stored. This may surprise many, but in the first case, decorators are not used for their intended purpose in relation to how they should be used in JavaScript code.

## Configuring TypeScript projects for Dependency Injection

If you want to take advantage of TypeScript for Dependency Injection, you need to write the following in your tsconfig files:

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

In addition, you need to add the `reflect-metadata` library as a project dependency, and also import it in the entry file (usually this file is called `main.ts`):

```ts
// main.ts
import "reflect-metadata";

// Your code here...
```

Please note that such an import must be unique for your entire project. In addition, for testing it is also necessary to make such an import if there is no `main.ts` import in a particular test.

## Unusual use of decorators in TypeScript projects

You may be wondering why the use of decorators might be unusual. Let's look at the simplest example first without a decorator and then with a fake decorator. So, let's say you have two classes, and the second class depends on the first class:

```ts
class Service1 {}

class Service2 {
  constructor(public service1: Service1) {}
}
```

If we run the TypeScript compiler on this file, it will output the following JavaScript code:

```js
class Service1 {
}
class Service2 {
    service1;
    constructor(service1) {
        this.service1 = service1;
    }
}
export {};
```

As you can see, without decorators, the TypeScript compiler does not transfer information about what type the `service1` property in `Service2` should be to the JavaScript code. In this case, this information is actually lost during compilation.

Now let's add any decorator:

```ts
class Service1 {}

@(fakeDecorator as any)
class Service2 {
  constructor(public service1: Service1) {}
}

function fakeDecorator() {}
```

This time, the TypeScript compiler will already output the following JavaScript code:

```js
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
class Service1 {
}
let Service2 = class Service2 {
    service1;
    constructor(service1) {
        this.service1 = service1;
    }
};
Service2 = __decorate([
    fakeDecorator,
    __metadata("design:paramtypes", [Service1])
], Service2);
function fakeDecorator() { }
export {};
```

As you can see, the very presence of any decorator at the class level signals the TypeScript compiler that it needs to transfer information about the constructor parameters of this class to the JavaScript code. This information is stored using the `reflect-metadata` library, more precisely - using the [Reflect][1] class from this library.

As you can guess, now it remains to read the stored information using the same class [Reflect][1].

## Conclusion

Therefore, if there is any decorator at the class level, the TypeScript compiler passes metadata from the TypeScript code to the JavaScript code using its own decorator, and the user-defined decorator is called dynamically within its own decorator body. Thus, you can use a function with an empty body as a decorator, but the TypeScript compiler will still transfer the metadata into the JavaScript code.


[1]: https://github.com/rbuckton/reflect-metadata?tab=readme-ov-file#api