Arrow functions do not function properly with Typescript decorators

I've created a typescript decorator factory that logs the total time taken to execute a function, along with the actual function execution results and parameters passed to the decorator.

For example:

export function performanceLog(...args: any[]) {
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
    var msg = '';
    if (args.length != 0) {
        msg = args.map(arg => arg).join(' ');
    }

    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key);
    }

    if (typeof descriptor.value !== 'function') {
        throw new SyntaxError('Only functions can be used with log decorators');
    }

    var originalMethod = descriptor.value.bind(target);

    descriptor.value = function() {
        var funcArgs: any = [];
        for (var i = 0; i < arguments.length; i++) {
            funcArgs[i - 0] = arguments[i];
        }
        var startTime = performance.now();
        console.log(`Begin function ${key} with params (${funcArgs}) : ${msg}`);
        var result = originalMethod.apply(this, funcArgs);
        var endTime = performance.now();
        console.log(`End function ${key}. Execution time: ${endTime - startTime} milliseconds. Return result : ${result}`);
        return result;
    };
    return descriptor;
};
}

I'm currently using this decorator with a function within a class: (assuming my class has other methods such as ctor, get, set, and utils).

class LoggerService {
    @performanceLog('validate', 'message')
    handleMessage(message) {
        console.log(message);
        return true;
    };
}

The function call looks like this:

handleMessage('decoratorValidation');

This gives me the following output:

Begin function handleMessage with params (decoratorValidation) : validate message     
decoratorValidation
End function handleMessage. Execution time: 0.3000000142492354 milliseconds. 
Return result : true

However, when I try to change the function handleMessage to use arrow function format (ES6), it throws an error:

@performanceLog('validate', 'message')
handleMessage = message => {
    console.log(message);
    return true;
};

Error Message:

Unable to resolve signature of property decorator when called as an expression.

I have ensured that all parameters are correctly configured in my tsconfig.json file. My entire project is targeted towards "ES6" and I would like the decorator to support arrow functions.

Answer №1

performaceLog is designed to function specifically with prototype methods as it relies on descriptor, which is considered optional.

In the case of handleMessage = message => ... class field, there is no descriptor because it does not exist on the class prototype. Class fields are simply assigned to this in the constructor.

The following line will not work for the same reason:

descriptor = Object.getOwnPropertyDescriptor(target, key);

To handle arrow methods in a decorator, a trap needs to be set on the class prototype. Below is an example of a versatile decorator that can be utilized with both prototype and instance methods; it uses get/set to capture the correct this context and caches the decorated function to the patchFn variable. It produces a descriptor regardless of whether the descriptor parameter is present:

function universalMethodDecorator(target: any, prop: string, descriptor?: TypedPropertyDescriptor<any>): any {
    let fn;
    let patchedFn;

    if (descriptor) {
        fn = descriptor.value;
    }

    return {
        configurable: true,
        enumerable: false,
        get() {
            if (!patchedFn) {
                patchedFn = (...args) => fn.call(this, ...args);
            }
            return patchedFn; 
        },
        set(newFn) {
            patchedFn = undefined;
            fn = newFn;
        }
    };

}

This pertains specifically to TypeScript decorators. Babel's legacy decorators may exhibit different behavior.

As elaborated in this response, prototype methods may be preferable over instance methods for various reasons. One of these reasons is the seamless application of decorators, since they are added to the class prototype. While arrow methods are naturally bound to the class instance, the decorator can bind prototype methods if necessary (which is essentially what universalMethodDecorator achieves; it has no effect on arrow methods).

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

Tips for updating a column with just one button in AngularJS

On my list page, there is an Unapproved button. I am new to angular and struggling to figure out how to retrieve the post's id and update the corresponding column in the database to mark it as approved. Can someone provide guidance on how to accomplis ...

summoning the iframe from a separate window

In my current setup, I have a link that passes a source to an iframe: <a href='new.mp4' target='showVideo'></a> <iframe src='sample.jpg' name='showVideo' ></iframe> However, what I would lik ...

What is the best way to prevent Firefox from storing the data of a textarea in the local environment?

I have been developing a website locally, and I have noticed that there are numerous <textarea> elements present on the site. One issue I am facing is that whenever I reload the site, the content within the <textarea> remains the same. This pe ...

Leveraging an AngularJS service within Angular framework

I am trying to incorporate an AngularJS service into my Angular project. Below is my main.ts file: import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app/app.module'; import {UpgradeMo ...

What is the best way to transfer the data from one JavaScript object to a new, empty object?

My Angular site requires a JavaScript object (JSON retrieved from a database) to display widgets: [{'widget_id':'1','widget_name':'Blue Widget','widget_description':'A nice blue widget','wid ...

Is my front-end JavaScript fetch request mistakenly being sent as a GET instead of a POST?

On clicking the submit button, a fetch request is triggered. Strangely, in the developer tools, it shows up as a GET request. I tested the request using Insomnia and it returned the handlebars site to me without any of my console logs appearing on either ...

Show side by side using javascript

My dilemma lies in having a set of cards that are meant to be displayed inline, but I have to initially hide them using "display: none". When a specific button is clicked, I aim to reveal these cards; however, upon doing so, each card seems to occupy its o ...

How can I retrieve the Google Maps URL containing a 'placeid' using AJAX?

I have a specific URL that I can access through my browser to see JSON data. The URL appears as follows: https://maps.googleapis.com/maps/api/place/details/json?placeid=ChIJZeH1eyl344kRA3v52Jl3kHo&key=API_KEY_HERE However, when I attempt to use jQuer ...

Add the content script to a webpage just one time

I am looking to inject a content script on context menu click in an extension manifest version 3. I need a way to determine if the script is already injected or not. If it hasn't been injected yet, I want to inject the content script. This condition m ...

How can I incorporate popups in React using mapbox-gl?

Currently utilizing mapbox-gl within React, everything seems to be functioning properly except for the integration of mapbox-gl's Popups. I have the function let Popup, but I am uncertain about how to incorporate it. renderMap() { if (this.props. ...

Using the quote and saying "quotation marks"

Any ideas on how to approach this? This is driving me crazy: $toReturn .= " function addProd(pExists) { document.getElementById('products').innerHTML = \"<tr><td id='prod_n'><input type='text&apos ...

Error: Trying to send FormData to ajax results in an Illegal Invocation TypeError being thrown

Successfully sending a file to the server for processing using the code below: var formData = new FormData(); formData.append('file', $('#fileUpload')[0].files[0]); options = JSON.stringify(options); // {"key": "value"} $.ajax({ ...

The file located at 'node_modules/minimatch/dist/cjs/index' does not contain an exported element called 'IMinimatch'. Perhaps you intended to reference 'Minimatch' instead?

I encountered an error while using rimraf as a devDependency (v5.0.0) in my project. The error message I received was: node_modules/@types/glob/index.d.ts:29:42 - error TS2694: Namespace '".../node_modules/minimatch/dist/cjs/index"' has ...

Can someone please explain the distinction between $http.get() and axios.get() when used in vue.js?

I'm feeling a little bit puzzled trying to differentiate between $http.get() and axios.get(). I've searched through various sources but haven't found any answers that fully satisfy me. Can someone please offer some assistance? ...

Steps to transfer the content of a label when the onclick event occurs

Seeking advice on how to send the dynamically varying value of a label upon clicking an anchor tag. Can anyone recommend the best approach to passing the label value to a JavaScript function when the anchor is clicked? Here is a sample code snippet: < ...

Step by step guide on transferring data from one Angular 7 component to another component following API response retrieval

I have been tackling an issue related to an API recently. Upon receiving a response from the API, my goal is to pass the value to a child component. In this component, I will run a method to obtain a response and display it on the screen. Example Code: ...

Preventing an Angular component from continuing to execute after clicking away from it

At the moment, I have a sidebar with clickable individual components that trigger API calls to fetch data. However, I've noticed that even when I click off a component to another one, the old component continues to refresh the API data unnecessarily. ...

What is the definition of the term "WebapiError"?

I'm currently developing a Spotify Web App that focuses on retrieving the top albums of KD Rusha using the Client ID and Artist ID to exclusively fetch his releases on Spotify. To accomplish this, I am utilizing an npm package called spotify-web-api-n ...

What is the best approach to transfer information from the client side to a node

I have an ejs page that looks like this: <%- include('../blocks/header', {bot, user, path}) %> <div class="row" style="min-width: 400px;"> <div class="col" style="min-width: 400px;"> <div class="card text-white mb-3" & ...

Is there a way to deactivate the click function in ngx-quill editor for angular when it is empty?

In the following ngx-quill editor, users can input text that will be displayed when a click button is pressed. However, there is an issue I am currently facing: I am able to click the button even if no text has been entered, and this behavior continues li ...