Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS TS apply decorator to all methods / enumerate class methods

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() {}
}
like image 427
Chris Avatar asked Dec 03 '17 17:12

Chris


People also ask

What is the use of decorator in Java?

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).

How to apply decorators in typescript classes?

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.

How to use decorator in Entity Framework?

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.

What order are decorators applied to a class?

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.


2 Answers

Create a class decorator and enumerate the properties on the target's prototype.

For each property:

  1. Get the property descriptor.
  2. Ensure it's for a method.
  3. Wrap the descriptor value in a new function that logs the information about the method call.
  4. Redefine the modified property descriptor back to the 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]
        };
    }
}
like image 176
David Sherret Avatar answered Oct 28 '22 19:10

David Sherret


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() {}
}
like image 11
Papooch Avatar answered Oct 28 '22 21:10

Papooch