Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Losing metadata #41

Open
RWOverdijk opened this issue Aug 14, 2016 · 2 comments
Open

Losing metadata #41

RWOverdijk opened this issue Aug 14, 2016 · 2 comments

Comments

@RWOverdijk
Copy link

Hello,

I've been trying to use this module using typescript for the decorators. I seem to keep losing my data and it's confusing me a little bit. Upon creating a small test, I found some odd behavior I wasn't expecting (I assume the problem here is me).

Whenever I put a decorator on a property, the decorator doesn't get the class as target. I have to use target.constructor to get it. But when I put the decorator on the class itself, it does seem to get the class.

Code speaks a thousand words, so:

export function field() {
  return (target:Object, property:string) => {
    console.log(target, target.constructor);
  };
}

@field() // Log above: [Function: Field]  [Function: Function]
export class Field {
  @field() // Log above: {}  [Function: Field]
  public property:any;
}

This confuses me. How can I always get the metadata of a class (including properties) if I always get a different target? Am I missing something on how this should be used?

Thank you for your time.

@rbuckton
Copy link
Owner

When you decorate a non-static property, method, or accessor, the target is always the prototype, as that is where the member will be installed (although non-static properties are actually installed on the instance).

When you decorate a static property, method, or accessor, the target is always the constructor, as that is where the member will be installed.

When you decorate a class, the target is always the constructor as it is not itself treated as a member.

The field() declaration on the class logs [Function Field] [Function: Function] because in that instance target is the constructor for Field and target.constructor points to the built-in Function (since the constructor of any function is always Function).

The field() declaration on the class logs {} [Function: Field] because in that instance target is the prototype for Field, and target.constructor points to the Field constructor.

Here are some other examples:

function info(target: any, property?: PropertyKey, descriptor?: PropertyDescriptor) {
  const data: string[] = [];
  if (arguments.length === 1) {
    data.push("class");
  }
  else {
    if (typeof target === "function") {
      data.push("static");
    }
    else {
      data.push("non-static");
    }
    if (!descriptor) {
      data.push("field");
    }
    else if (descriptor.get || descriptor.set) {
      data.push("accessor");
    }
    else {
      data.push("method");
    }
    data.push(JSON.stringify(propertyKey));
  }
  console.log(data.join(" "));
}

@info // logs: class
class Sample {
  @info static a = 1; // logs: static field "a"
  @info static b() {} // logs: static method "b"
  @info static get c() { return 1; } // logs: static accessor "c"

  @info x = 1; // logs: non-static field "x"
  @info y() {} // logs: non-static method "y"
  @info get z() { return 1; } // logs: non-static accessor "z"
}

With the Reflect.metadata decorator, the metadata is attached to the target. For class decorators or static members this will always be the constructor. For non-static members this will always be the prototype. If you want to get metadata for an instance member, you can do:

Reflect.getMetadata(metadataKey, instance, memberName);

If you want to get metadata for a static member, you can do:

Reflect.getMetadata(metadataKey, Field, memberName); 

If you want to get metadata for a class, you can do:

Reflect.getMetadata(metadataKey, Field);

@RWOverdijk
Copy link
Author

@rbuckton Thank you for this. This is very helpful. So, if I want to collect all the metadata on the constructor, I just have to check what I'm getting. If it's a constructor use that, otherwise use the constructor passed to me?

It's for an orm. In the end I want to have a mapping key with an object that describes everything in the class. Does that make sense, or am I then abusing metadata?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants