DEV Community

Костя Третяк
Костя Третяк

Posted on

Passing metadata from TypeScript code to JavaScript code using decorators for Dependency Injection

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:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
Enter fullscreen mode Exit fullscreen mode

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):

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

// Your code here...
Enter fullscreen mode Exit fullscreen mode

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:

class Service1 {}

class Service2 {
  constructor(public service1: Service1) {}
}
Enter fullscreen mode Exit fullscreen mode

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

class Service1 {
}
class Service2 {
    service1;
    constructor(service1) {
        this.service1 = service1;
    }
}
export {};
Enter fullscreen mode Exit fullscreen mode

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:

class Service1 {}

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

function fakeDecorator() {}
Enter fullscreen mode Exit fullscreen mode

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

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 {};
Enter fullscreen mode Exit fullscreen mode

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 class from this library.

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

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.

Top comments (0)