reflect-metadata
reflect-metadata copied to clipboard
How to remember what properties with my decorator?
Image this example:
class Pet extends Model {
@field({ required: true })
id: number
}
class Cat extends Pet {
@field({ min: 3, max: 20 })
name: string
}
field
decorator write options into metadata Reflect.defineMetadata('orm:options', options, target, key)
Then how could I get all properties ['name', 'id']
with @field
?
Now my codes like this. But I wish a better way:
function field(target, key, desc, [options]) {
Reflect.defineMetadata('orm:options', options, target, key)
Reflect.defineMetadata(`orm:key:${key}`, true, target)
}
class Model {
static getFieldKeys() {
return Reflect.getMetadataKeys(this.prototype)
.filter(k => k.startsWith('orm:key:'))
.map(k => k.substr(8))
}
}
Your decorator can hold the option inside itself, like this:
class Pet extends Model {
id: number
}
class Cat extends Pet {
@field({ min: 3, max: 20 })
name: string
}
function field({ min: number, max: number }) {
let _val: any;
return function (target: any, key: string) {
const getter = function () {
return _val;
};
const setter = function (newVal) {
if (newVal.length <= max && newVal.length >= min) {
_val = newVal;
}
};
Object.defineProperty(target, key, {
get: getter,
set: setter
});
}
}
This way you don't have to expose metadata and the logic will stay inside your decorator.
Kind of misses the point of a metadata-based approach to decorators, no? The whole point is to avoid modifying behaviour of an object via the presence of the annotation. Instead, allow the annotation to be given meaning AT RUN TIME. That way, I can use the object in a context that doesn't require the annotation without any change in behaviour or code. The annotation creates metadata that is available via reflection in order to conditionally apply the annotation or not during code execution. If you will be using it in a context that requires the annotations, pass class definitions or class instances through a function that 'enables' an annotation by looking at the metadata and applying the functionality that is appropriate. This is actually why it is useful to be able to say "give me all of the methods of the target that have been annotated with key 'x', because now I can just iterate over all of them, wrapping each with the decorator functionality. Without that, I have to iterate over every property, checking to see if it has a particular metadata key, and then make the change. It's not a particularly big change. It's just a convenience function in the API that's being asked for, though it would probably be more efficient to map in both directions so that using the convenience function is more than just a macro for iterating over all properties, building a list with a particular metadata. You can implement the reverse map in a slightly simpler manner than the OP did, however:
function field(target, key, desc, [options]) {
Reflect.defineMetadata('orm:options', options, target, key)
let keys = Reflect.getMetadata('orm:keys', target)
if (keys == undefined) {
keys = { 'field': new Set(key) }
} else {
// also should check that 'field' key has an object before callling method on it.
keys['field'].add(key)
}
Reflect.defineMetadata('orm:keys', keys, target)
}
class Model {
static getFieldKeys() {
return Reflect.getMetadata('orm:keys', this.prototype)
}
}
You can store lists and maps in metadata - so just maintain the Set of fields that have been seen by a particular decorator in the metadata. If you don't have multiple decorators with different names, there's no need for a map of Sets, just store the Set directly into 'orm:fields'
I want to use Reflect.getMetadata("design:type", target, propertyKeys) for get all info of class, but it define by typescript, not me.
maybe support some api to get property names will be better?
You can easily implement a reverse lookup yourself. When you write the design:type metadata into target.property, you can also write design:types metadata into target - maintaining a list of propertyKeys that have been seen. You can retrieve the set of properties which have the annotation by simply looking up design:types and then iterating over each value, looking up design:type for target.propertyKey. It requires so little code to do this that there really doesn't seem to be much reason to insist upon its inclusion in the reflect-metadata API itself.