Add a decorator to all functions in a TypeScript class to list all available methods

How can I apply a decorator function to all methods within a class in order to streamline the code like this:

class User {
    @log
    delete() {}

    @log
    create() {}

    @log
    update() {}
}

and have it transformed into:

@log
class User {
    delete() {}
    create() {}
    update() {}
}

Answer №1

To enhance a class with a decorator, iterate through the properties on its prototype.

Follow these steps for each property:

  1. Retrieve the property descriptor.
  2. Confirm that it pertains to a method.
  3. Wrap the method's value in a new function that logs details about the method call.
  4. Reassign the modified property descriptor to the property.

Adapting the property descriptor is crucial to ensure compatibility with other decorators affecting it.

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);        
    }
}

Enhancing Base Class Methods

If you wish to affect base class methods as well, consider implementing 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]
        };
    }
}

Answer №2

If you happen to come across this message at some point down the road:

After being inspired by David's response, I decided to put my own spin on it and eventually turned it into an npm package: https://www.npmjs.com/package/decorate-all

In the original poster's situation, the usage would look something like this

@DecorateAll(log)
class User {
    delete() {}
    create() {}
    update() {}
}

Answer №3

If you prefer not to add extra dependencies, here is a concise version based on @Papooch's code

function ApplyDecorators(decorator: MethodDecorator) {
    return (target: any) => {
        const properties = Object.getOwnPropertyDescriptors(target.prototype);
        for (const [propName, descriptor] of Object.entries(properties)) {
            const isFunction =
                typeof descriptor.value == "function" &&
                propName != "constructor";
            if (!isFunction) {
                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);
    };
}

@ApplyDecorators(Throttle)
class APIHandler {
    async call1() {    }
    async call2() {    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering an issue with a Discord bot causing it to malfunction and deviate from its intended

Initially, everything appears to be functioning properly with the bot. When I execute the ?listen command, it responds correctly with bot is collecting messages now.... However, the ?stop command does not seem to have any effect. Furthermore, when I try th ...

Performing an API GET request in a header.ejs file using Node.js

Looking to fetch data from an endpoint for a header.ejs file that will be displayed on all routed files ("/", "/news" "/dogs"). Below is my app.js code: // GET API REQUEST var url = 'https://url.tld/api/'; request(url, function (error, response, ...

Ensure that the jQuery ajax function triggers only after the images or iframes have completely loaded

I am currently in the process of creating an online portfolio. My goal is to have project information load into the current page via ajax when a user clicks on a specific project. However, I am facing an issue with the timing of the load() success function ...

Deliver asynchronous requests using Web Components (Model-View-Controller)

I am currently working on developing an application using pure javascript and Web Components. My goal is to incorporate the MVC Pattern, but I have encountered a challenge with asynchronous calls from the model. Specifically, I am creating a meal-list com ...

Enhancing functionality with jQuery: updating multiple input fields at

Currently, I am attempting to utilize jQuery to modify some HTML text by adjusting a slider. I have managed to accomplish this; however, I also need it to happen only if a checkbox is checked. How can I integrate both conditions and ensure that the text ch ...

How to stop a checkbox from being selected in Angular 2

I have a table with checkboxes in each row. The table header contains a Check All checkbox that can toggle all the checkboxes in the table rows. I want to implement a feature where, if the number of checkboxes exceeds a certain limit, an error message is ...

React Intersection Observer not functioning properly

Hey there! I'm trying to create an animation where the title slides down and the left element slides to the right when scrolling, using the intersection observer. Everything seems to be fine in my code, but for some reason it's not working. Any t ...

I am experiencing an issue with my d3 force directed graph where the links are not

I am relatively new to d3 and have limited experience with web frontend development. In my current web application project, I am attempting to create a force directed graph. Despite spending several hours trying to make it work, I have been unable to displ ...

Updating NPM packages versions is currently restricted

I'm in the process of creating a Next.JS application using create-next-app. However, I've noticed that in the package.json file it lists the following dependencies: "eslint": "8.43.0", "eslint-config-next": &quo ...

Intellisense in VS Code is failing to work properly in a TypeScript project built with Next.js and using Jest and Cypress. However, despite this issue,

I'm in the process of setting up a brand new repository to kick off a fresh project using Next.js with TypeScript. I've integrated Jest and Cypress successfully, as all my tests are passing without any issues. However, my VSCode is still flagging ...

Seeking a window-adjustable table with stationary headers

I have been searching high and low for a solution to my issue but have come up empty-handed. My goal is to create a table with fixed headers that remain visible while scrolling through the rest of the table. The catch is, I also need it to align properly w ...

Show mistakes using source mapping (TypeScript combined with Node/Express)

In my Docker container, I have a node instance running express. Whenever I intentionally cause an error in my simple app.ts file, like below: // Start listening for requests app.listen(3000, () => { console.log('Application Service starting!&ap ...

Displaying Grunt Command-Line Output in Browser

Is there a straightforward method to display the command-line output of a grunt task in a browser? Essentially, I want to showcase the input and/or output of a command line process within a browser window. While I am aware that I could develop a custom app ...

Secret method to successfully reach a function designated within the load scope

Check out my code : <a href="javascript:void(0);" onclick="myFunction(this)">Call Function</a>​ $(window).load(function () { function myFunction(param) { console.log("called"); } }); It seems like I'm unable to access ...

The specified "ID" type variable "$userId" is being utilized in a positional context that is anticipating a "non-null ID" type

When attempting to execute a GraphQL request using the npm package graphql-request, I am exploring the use of template literals. async getCandidate(userId: number) { const query = gql` query($userId: ID){ candidate( ...

Navigate to a precise section of the webpage, positioned at a specific distance from the top

When scrolling on my page, the static header moves along with the user. However, when I link to a specific div using the standard method, the div appears behind the header. I would like to utilize: <a href="#css-tutorials">Cascading Style Sheet Tut ...

Tips for redirecting JavaScript requests to the target server using curl on a server

Currently, I am running a crawler on my server and require the execution of JavaScript to access certain data on the target site that I want to crawl. I recently had a question about a different approach to this issue, but for now, I need help with the fol ...

Tips for reverting from Angular 7 to Angular 6

I attempted to switch from angular 7 back to angular 6 by executing the following npm commands: npm uninstall -g angular-cli npm cache clean npm install -g <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="32535c55475e53401f515e5 ...

How about beginning a JavaScript count with a randomly generated number?

As I work on developing this code, I am faced with a challenge: /** * Increment value with random intervals. * @param {string} id - Id of DOM Element. * @param {number} start - Start counter value. Applied immediately- * @param {number} end - End c ...

Tips for retrieving a cropped image with Croppr.js

Currently, I am developing a mobile application using Ionic 3. Within the application, I have integrated the Croppr.js library to enable image cropping before uploading it to the server. However, I am facing an issue where I am unable to retrieve the cropp ...