I am building a simple mapping between frontend/backend data structure. In order to do that I've created a decorator that looks like the following:
function ApiField(
apiKey: string,
setFn: (any) => any = (ret) => ret,
getFn: (any) => any = (ret) => ret
) {
return function (target: AbstractModel, propertyKey: string) {
target.apiFieldsBag = target.apiFieldsBag || {};
_.assign(
target.apiFieldsBag,
{
[propertyKey]: {
apiKey: apiKey,
setFn: setFn,
getFn: getFn
}
}
);
};
}
And this is how I use it:
class AbstractCar {
@ApiField('id')
public id: string = undefined;
}
class BMW extends AbstractCar {
@ApiField('cylinders')
public cylinderCount: number;
}
class VW extends AbstractCar {
@ApiField('yearCompanyFounded')
public yearEstablished: number;
}
The issue that I'm seeing is that instead of the actual object being passed to the decorator it's always its prototype:
__decorate([
ApiField('yearCompanyFounded')
], VW.prototype, "yearEstablished", void 0);
Which means that as I am assigning stuff to the instance in the decorator, it is always attached to the prototype which in turn means that properties I want to be defined only the VW
instance are also available on the AbstractCar
and the BMW
class (in this example this would be yearEstablished
). This makes it impossible to have two properties with the same name but different API fields in two different classes.
Is there any way to circumvent this behaviour?
Right now, all three classes are adding properties to the same object. The key to fix this is to clone the object on target.data
so that each class is using a different object instead of all of them referring to the same object.
Here's a simpler example that demonstrates one way of doing this:
function ApiField(str: string) {
return function (target: any, propertyKey: string) {
// I tested with Object.assign, but it should work with _.assign the same way
target.data = _.assign({}, target.data, {
[propertyKey]: str
});
};
}
class AbstractCar {
@ApiField("car")
public carID;
}
class BMW extends AbstractCar {
@ApiField("bmw")
public bmwID;
}
class VW extends AbstractCar {
@ApiField("vw")
public vwID;
}
AbstractCar.prototype.data; // Object {carID: "car"}
BMW.prototype.data; // Object {carID: "car", bmwID: "bmw"}
VW.prototype.data; // Object {carID: "car", vwID: "vw"}
The problem is that public
inside a class is not standard JavaScript, it’s only something that TypeScript does. Therefore, you have to be careful, because anything you do may break in the future.
One possibility is to use Object.assign()
to add instance properties (IINM, apiFieldsBag
should be transferred from the object created by the object literal to this
):
class AbstractCar {
constructor() {
Object.assign(this, {
@ApiField('id')
id: undefined,
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With