TypeScript method decorators example
Tags: typescript, javascript
Published: 2018-03-25 09:12:11

---
title: TypeScript method decorators example
published: true
description: We're going to learn how to implement a method decorator using a concrete example
tags: TypeScript, JavaScript
---

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:

![Wikipedia schema](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Decorator_UML_class_diagram.svg/800px-Decorator_UML_class_diagram.svg.png)
Source: https://en.wikipedia.org/wiki/Decorator_pattern

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 concept](https://thepracticaldev.s3.amazonaws.com/i/oppthnv7npyr0mtrd4js.png)

Which ultimately looks like this:

![Debounce example](https://thepracticaldev.s3.amazonaws.com/i/2i4vie4kb6i0t75xbeha.gif)

## 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:
```bash
tsc --target ES5 --experimentalDecorators
```

tsconfig.json:
```json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}
```

## Create the decorator

A decorator factory is declared as a normal TypeScript function :
```ts
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):
```ts
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:
```ts
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:
```ts
var originalMethod = descriptor.value;
```

Then we change it by setting `descriptor.value` to a new function:
```ts
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:

```ts
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):
```ts
search(query: string) {
    axios.get(`users`, {
        params: {
            name: query
        }
    })
    .then(response => {
        // ...
    })
}
```

We simply apply our previously defined decorator, with the right amount of time:
```ts
@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](https://vuejs.org/) with TypeScript, they're a great match!
> https://fr.vuejs.org/v2/guide/typescript.html

To dig deeper into TypeScript decorators:
https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841
http://www.typescriptlang.org/docs/handbook/decorators.html
http://www.sparkbit.pl/typescript-decorators/
https://cabbageapps.com/fell-love-js-decorators/

**Originally published on [my personal blog](http://cleriotsimon.com/posts/typescript-decorators/).**