just Nik just Nik - 5 months ago 11
Javascript Question

Assigning properties to non-prototype with decorators

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?

Answer

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"}
Comments