Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

can typescript property decorators set metadata for the class?

In typescript, is it possible to use a property decorator to set metadata for the class? Consider the code below. The "target" of the class decorator is apparently not the same as the "target" of the property decorator. Can I derive one from the other?

import 'reflect-metadata';


const MY_CLASS_DECORATOR_KEY = 'MyClassDecoratorKey';
const MY_PROPERTY_DECORATOR_KEY = 'MyPropertyDecoratorKey';

export const MyClassDecorator = options => {
    return function (target) {
      console.log('class target: ' , target);
      Reflect.defineMetadata(MY_CLASS_DECORATOR_KEY, options, target);
    };
};

export const MyPropertyDecorator = (options): PropertyDecorator => {
    return (target, property) => {
      console.log('property target: ' , target);
      const metadata = Reflect.getMetadata(MY_PROPERTY_DECORATOR_KEY, target) || {};
      metadata[property] = options;
      Reflect.defineMetadata(MY_PROPERTY_DECORATOR_KEY, metadata, target);
    };
};

@MyClassDecorator('my class decorator value')
class MyClass {
    @MyPropertyDecorator('first my property decorator value')
    myFirstProperty: any;

    @MyPropertyDecorator('second my property decorator value')
    mySecondProperty: any;
}

console.log('keys: ', Reflect.getMetadataKeys(MyClass));

Note the output:

property target:  MyClass {}
property target:  MyClass {}
class target:  function MyClass() {
    }
keys:  [ 'MyClassDecoratorKey' ]

How can I get the metadata keys to also show keys from the property decorator?

like image 890
bkinsey808 Avatar asked Feb 16 '17 17:02

bkinsey808


1 Answers

Yes you are free to do whatever you want in your decorator, but as you found out, your issues is with the target you are being passed.

Basically, in a property decorator, the target parameter can be one of two things depending on whether the decorator is used on a static property or an instance property:

On a static property, the target property will be the class constructor function. This means that on a static property the target will be exactly the same as it is for your class decorator.

However, on an instance property, the target parameter will be the prototype of the class you created, not the constructor. This is why you are seeing the behavior you are seeing. In the case of an instance property your metadata is not being attached to the constructor as is the case with your class decorator.

There is still hope though, because you can easily get to the constructor function given a prototype instance as it is stored in a property called constructor. So in your case, you can get the behavior you are looking for by doing this:

export const MyPropertyDecorator = (options): PropertyDecorator => {
    return (target, property) => {
      var classConstructor = target.constructor;
      console.log('property target: ' , classConstructor);
      const metadata = Reflect.getMetadata(MY_PROPERTY_DECORATOR_KEY, classConstructor) || {};
      metadata[property] = options;
      Reflect.defineMetadata(MY_PROPERTY_DECORATOR_KEY, metadata, classConstructor);
    };
};

NOTE: the above change would work for instance properties, but not static properties. If you need to handle both types of properties you would need to add some additional logic to determine whether to use target or target.constructor

like image 163
Daniel Tabuenca Avatar answered Oct 30 '22 12:10

Daniel Tabuenca