Get Off My Lawn Get Off My Lawn - 3 months ago 27
TypeScript Question

Reflect decorator isn't saving/retrieving data for an object

What am I doing wrong? I have the following decorator functions setup to hold metadata about a class, and they both look like this (one to set the data and one to get the data):

function setComponentMenu(text: string): any {
return Reflect.metadata('componentPath', text);
}
function getMenuPath(target: any): any {
return Reflect.getMetadata('componentPath', target);
}


I am then using it like this, and the
customEditor
decorator works just fine, and puts an instance of the
CameraEditor
class into
Globals.editors
:

@customEditor(Camera)
@setComponentMenu('Renderers/Camera')
class CameraEditor extends Editor {

}


After the value has been set, and I try to get it back like this:

for(let i = 0; i < Globals.editors.length; i++){
let editor: Editor = Globals.editors[i];
let path = getMenuPath(editor);
console.log(path);
}


I am getting
undefined
, which is fine for items that don't have the
@setComponentMenu
but the
CameraEditor
does have it so I would expect the console to display
Renderers/Camera
.

What is wrong?

Answer

Class decorators needs to have the following signature:

function decorator(constructor: Function)

If you want to pass values to it then you need to use a decorator factory:

function setComponentMenu(text: string) {
    return (constructor) => {
        ...
    }
}

Also, you should not return a value for your class decorator as:

If the class decorator returns a value, it will replace the class declaration with the provided constructor function.

NOTE Should you chose to return a new constructor function, you must take care to maintain the original prototype. The logic that applies decorators at runtime will not do this for you.

Unless you want to replace the constructor of course.

I wasn't able to make it work using Reflect.metadata, but I came up with a working solution using Reflect.defineMetadata:

function setComponentMenu(text: string) {
    return (constructor) => {
        Reflect.defineMetadata("componentPath", text, constructor, "class");
    }
}

function getMenuPath(target: any): any {
    return Reflect.getMetadata("componentPath", target.constructor, "class");
}

You can save the metadata info to the prototype instead of the constructor:

function setComponentMenu(text: string) {
    return (constructor) => {
        Reflect.defineMetadata("componentPath", text, constructor.prototype, "class");
    }
}

function getMenuPath(target: any): any {
    return Reflect.getMetadata("componentPath", target.constructor.prototype, "class");
}