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() {}
}
The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition. A class decorator cannot be used in a declaration file, or in any other ambient context (such as on a declare class).
In this section, you will apply decorators in TypeScript classes. In TypeScript, you can create decorators using the special syntax @expression, where expression is a function that will be called automatically during runtime with details about the target of the decorator. The target of a decorator depends on where you add them.
The decorator class is a function and gets the constructor as a parameter, then include the id and created properties. The decorator is ready to be used in each entity without modifying his constructor or extend, only needs to add @Entity before class definition.
There is a well defined order to how decorators applied to various declarations inside of a class are applied: Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static member.
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]
};
}
}
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() {}
}
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