Reputation: 14260
I would like to apply a decorator function to all methods within a class so I can replace:
class User {
@log
delete() {}
@log
create() {}
@log
update() {}
}
with
@log
class User {
delete() {}
create() {}
update() {}
}
Upvotes: 29
Views: 8486
Reputation: 2265
If you don't feel like pulling additional dependencies, here's a simplified version of @Papooch's implementation
function DecorateAll(decorator: MethodDecorator) {
return (target: any) => {
const descriptors = Object.getOwnPropertyDescriptors(target.prototype);
for (const [propName, descriptor] of Object.entries(descriptors)) {
const isMethod =
typeof descriptor.value == "function" &&
propName != "constructor";
if (!isMethod) {
continue;
}
decorator(target, propName, descriptor);
Object.defineProperty(target.prototype, propName, descriptor);
}
};
}
function Throttle(
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function () {
console.log("throttle");
return original.call(this);
};
}
@DecorateAll(Throttle)
class SharedApiClient {
async fetch1() { }
async fetch2() { }
}
Upvotes: 3
Reputation: 1645
For whomever stumbles upon this in the future:
I took inspiration in David's answer and created my own version. I later made it into a npm package: https://www.npmjs.com/package/decorate-all
In OP's scenario, it would be used like this
@DecorateAll(log)
class User {
delete() {}
create() {}
update() {}
}
Upvotes: 12
Reputation: 106900
Create a class decorator and enumerate the properties on the target's prototype.
For each property:
It's important to modify the property descriptor because you want to ensure your decorator will work well with other decorators that modify the property descriptor.
function log(target: Function) {
for (const propertyName of Object.keys(target.prototype)) {
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, propertyName);
const isMethod = descriptor.value instanceof Function;
if (!isMethod)
continue;
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("The method args are: " + JSON.stringify(args));
const result = originalMethod.apply(this, args);
console.log("The return value is: " + result);
return result;
};
Object.defineProperty(target.prototype, propertyName, descriptor);
}
}
Base Class Methods
If you want this to affect the base class methods as well, then you might want something along these lines:
function log(target: Function) {
for (const propertyName in target.prototype) {
const propertyValue = target.prototype[propertyName];
const isMethod = propertyValue instanceof Function;
if (!isMethod)
continue;
const descriptor = getMethodDescriptor(propertyName);
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("The method args are: " + JSON.stringify(args));
const result = originalMethod.apply(this, args);
console.log("The return value is: " + result);
return result;
};
Object.defineProperty(target.prototype, propertyName, descriptor);
}
function getMethodDescriptor(propertyName: string): TypedPropertyDescriptor<any> {
if (target.prototype.hasOwnProperty(propertyName))
return Object.getOwnPropertyDescriptor(target.prototype, propertyName);
// create a new property descriptor for the base class' method
return {
configurable: true,
enumerable: true,
writable: true,
value: target.prototype[propertyName]
};
}
}
Upvotes: 34