TypeScript 1.5, reflection and decorator

mfrachet picture mfrachet · Jun 5, 2015 · Viewed 7.5k times · Source

I was wondering if it was possible to use reflection, and more particularly class reflection concerning decorator, on property, on parameter or on method ?

Is this possible to use reflector to get the information used in the decorators ?

If it's possible, how can we do that ? Else, why cant we do that ?

EDIT :

I have an application in which I use decorators, to inject data, from a serviceRegistry, to a decorator called @Inject.

In this application, I fill a simple service registry by hand, like :

serviceRegistry.push(MyClass1)
serviceRegistry.push(MyClass2)
...
serviceRegistry.push(MyClass100)

The aim is to be able to fill this service registry with classes that are decorated with @ToInject annotation, at runtime.

This will permit me to avoid to fill this registry by hand, and to automate this simply.

THanks for advance

Answer

Remo H. Jansen picture Remo H. Jansen · Aug 27, 2015

You can use reflection by importing the reflect-metadata package.

import 'reflect-metadata';

Use it with TypeScript 1.5 and the compiler flag emitDecoratorMetadata set to true. Don't forget including a reference to reflect-metadata.d.ts as well.

You need to implement your own decorators:

// declare property decorator
function logType(target : any, key : string) {
  var t = Reflect.getMetadata("design:type", target, key);
  console.log(`${key} type: ${t.name}`);
}

class Demo{ 
  @logType // apply property decorator
  public attr1 : string;
}

It will log in console:

attr1 type: String

Another example:

// declare parameter decorator
function logParamTypes(target : any, key : string) {
  var types = Reflect.getMetadata("design:paramtypes", target, key);
  var s = types.map(a => a.name).join();
  console.log(`${key} param types: ${s}`);
}  

class Foo {}
interface IFoo {}

class Demo{ 
  @logParameters // apply parameter decorator
  doSomething(
    param1 : string,
    param2 : number,
    param3 : Foo,
    param4 : { test : string },
    param5 : IFoo,
    param6 : Function,
    param7 : (a : number) => void,
  ) : number { 
      return 1
  }
}

It will log in console:

doSomething param types: String, Number, Foo, Object, Object, Function, Function

Notice the that interfaces IFoo and object literal { test : string} are serialized as Object. The serialization rules are:

  • number serialized as Number
  • string serialized as String
  • boolean serialized as Boolean
  • any serialized as Object
  • void serializes as undefined
  • Array serialized as Array
  • If a Tuple, serialized as Array
  • If a class serialized it as the class constructor
  • If an Enum serialized it as Number
  • If has at least one call signature, serialized as Function
  • Otherwise serialized as Object (Including interfaces)

Interfaces and object literals may be serialize in the future via complex type serialization but this feature is not available at this time.

You can also get the return type of a function using:

Reflect.getMetadata("design:returntype", target, key);

If you need more info about decorators you can read : Decorators & metadata reflection in TypeScript: From Novice to Expert