Reflection in Typescript

Check reflection state in Typescript

Sergey Radzishevskii
4 min readMay 22, 2020

The reflection we are used to.

Languages like C#, Java, PHP have a reflection mechanism.

Reflection is a feature in the Java programming language. It allows an executing Java program to examine or “introspect” upon itself, and manipulate internal properties of the program. For example, it’s possible for a Java class to obtain the names of all its members and display them.

Reflect has been introduced in Javascript. But that’s not what we’re used to in other languages. Most of the features have been available via “proxy handler” or raw Object. Reflect functionality is quite poor. Javascript doesn’t have a strong type system, interfaces, abstract/final classes so you cannot

  • get metadata about argument or types
  • define if a class implements a certain interface
  • define if a class is abstract
  • define if a method is public/private
  • etc

Basically we can get all information about a class using Reflection.

But what about typescript?

Typescript has classes, interfaces, visibility, and strict types. So potentially we could get meta-information about it during TS runtime. Unfortunately, it’s not so easy… and frankly, it’s not possible. Typescript code, in the end, will be transformed into Javascript.

interface Bar {
buzz: (a: string) => string;
}
class Foo implements Bar {
public buzz(a: string): string {
return '';
}
static b: string = '';
}

Will be translated to

var Foo = (function () {
function Foo() {}
Foo.prototype.buzz = function (a) {
return '';
};
Foo.b = '';
return Foo;
}());

As you can see JS version doesn’t know anything about Bar interface, argument types don’t exist. All reflection features are not available at JS runtime.

But how does reflection work in Typescript…

Typescript has extended reflect-metadata it allows you to get function argument and return types in conjunction with emitDecoratorMetadata. Let’s looks at how it works. We will need to enable

"experimentalDecorators": true,
"emitDecoratorMetadata": true

Now if we add some ourCustomDecorator

class Foo {
@ourCustomDecorator
public buzz(a: string): string {
return '';
}
}

Our javascript will be translated to

var __decorate = (... 
var __metadata = (...
__decorate([
ourCustomDecorator,
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", String)
], Foo.prototype, "buzz", null);

Did you expect some magic?

Now you can get metadata info

console.log(Reflect.getMetadata("design:type", target, propertyKey));
// [Function: Function]
// Checks the types of all params
console.log(Reflect.getMetadata("design:paramtypes", target, propertyKey));
// [[Function: Number]]
// Checks the return type
console.log(Reflect.getMetadata("design:returntype", target, propertyKey));

You can find more docs on typescript site and we are going to discuss the approach here.

As you can see emitDecoratorMetadata will add metadata in inline style to Reflect store/object and it’s very straightforward. But this approach is quite poor and robs us of most of Reflect functionality that we getting used to in other languages.

Pros

We can get paramtypes info for the constructor and methods. That feature is widely used in DI libraries like tsyringe and inversify

@injectable()
class Foo {
constructor(private database: Database) {}
}

When you decorate a class — you can get constructor metadata.

Cons

  • decorators and reflection are mixed together. It’s quite different features but the current approach doesn’t allow using metadata without decorators, that are still experimental BTW.
  • We deal with a target, but not with a class — Reflect.getMetadata(“design:type”, target, propertyKey). It’s not convenient since the target can be not available.
  • In order to get metadata for your method, you need to wrap it with a@decorator On the other hand, if you don’t need meta but need a decorator — still metadata will be injected. Basically, __metadata will be injected into the final JS code even if it does not really need it.
  • Missing other Reflection features described above — visibility, access to class methods, constants, etc.

Summary

The reality is that Typescript Reflection is very poor. In order to get a very basic set of Reflection features — we need to make a number of hacky solutions. Other than Dependency Injection tools, I see no other use for this limited functionality. And remember that there’s an awesome DI package that doesn’t require decorators and metadata.

In the end, I ask myself — should I use decorators and metadata at Typescript? And for now, I answer NO. Please comment — do you know other examples when TS Reflection is really useful.

--

--

Sergey Radzishevskii
Sergey Radzishevskii

Written by Sergey Radzishevskii

Enjoy turning complex systems into intelligible architectures chilling in my garden.

Responses (5)