Decorators are a stage 2 proposal for JavaScript already available as an experimental feature in TypeScript.

A decorator is a function that applies to a class, method, property or parameter and adds some logic to the latter. In other word, using a decorator is the same (but far simpler) as creating a new class that extends the target class and has a field pointing to it:

You can have even have decorator factories that customize how the decorator is applied to a declaration (making it more easily reusable in different contexts).

In this example, we’re going to use a method decorator factory to debounce a function.

Debounce concept

The perfect example for debounce is a realtime-search text input : in order to make a search, you send a request to the server. The naive implementation would send a request each time the text input changes, but it would overload the server and the view would have to wait for every request to finish before displaying the final results.

Debouncing delays the execution of a method for a fixed duration. If the method is called again while being stalled, the first method call is canceled. That way, the search component would only send one request to the server, when the user stops typing.

Debounce schema

Which ultimately looks like this:

Debounce example

TypeScript configuration

Decorators are still an experimental feature in TypeScript so you need to explicitly enable experimentalDecorators compiler option. Two possibilities there, depending on how you’re building your project:

Command line
tsc --target ES5 --experimentalDecorators
tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

Create the decorator

A decorator factory is declared as a normal TypeScript function :

function Debounce(wait:number, immediate:boolean=false) {

wait is the millisecond delay.
immediate sets if we want to "call the function then stall for x milliseconds before allowing it to be called again", or "stall for x milliseconds then actually call the function".

The Debounce method is going to return the decorator function (that’s why it’s called a factory):

return function(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {

This function gets its three parameters from the element it’s applied on (in our example it’s going to be the method we want to debounce):

  • target references the element’s class. It will be the constructor function for a static method or the prototype of the class for an instance member

  • propertyKey is the name of the element

  • descriptor is the Property Descriptor of the method, so we can alter it

The body of the decorator function looks like this:

var timeout:any;
var originalMethod = descriptor.value;
descriptor.value = function() {
    var context = this
    var args = arguments;
    var later = function() {
        timeout = null;
        if (!immediate) originalMethod.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) originalMethod.apply(context, args);
};
return descriptor;

We store the original body of the method:

var originalMethod = descriptor.value;

Then we change it by setting descriptor.value to a new function:

descriptor.value = function() {

We then use timeouts to delay the method execution.

The value returned by our method decorator will override the original class method’s descriptor.

In the end we have the following debounce decorator factory:

function Debounce(wait:number, immediate:boolean=false) {
    return function(target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
        var timeout:any;
        var originalMethod = descriptor.value;
        descriptor.value = function() {
            var context = this
            var args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) originalMethod.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) originalMethod.apply(context, args);
        };
        return descriptor;
    }
};

Use it

We want to debounce the following search method (from a Vue.js component’s class):

search(query: string) {
    axios.get(`users`, {
        params: {
            name: query
        }
    })
    .then(response => {
        // ...
    })
}

We simply apply our previously defined decorator, with the right amount of time:

@Debounce(500)
search(query: string) {
    axios.get(`users`, {
        params: {
            name: query
        }
    })
    .then(response => {
        // ...
    })
}

And that’s it, the search method is going to be called only if no other search call is sent during 500ms.

You should definitely consider using Vue.js with TypeScript, they’re a great match!
https://fr.vuejs.org/v2/guide/typescript.html