TypeScript Functional Decorators w/ usecases

TypeScript Functional Decorators w/ usecases

> Generally, decorators in programming are used to add some additional meaning/function to a method, variable or a class.

I have used a lot of decorators while working in Spring Boot. I am currently learning Typescript and decorators fascinated me. Here is a small illustration of my use case for typescript decorators for functions.

Consider a Human class which maintains the stamina attribute and it is manipulated by run and rest methods. We need to capture the value of stamina before and after both, the actions - run and rest. I have added snippets for the code with and without decorators.

Code Before Decorators

class Human {
    stamina: number;
    constructor(stamina: number) {
        this.stamina = stamina;
    }

    run(requiredStamina: number): number {
        return (this.stamina -= requiredStamina);
    }

    rest(addedStamina: number): number {
        return (this.stamina += addedStamina);
    }
}

const human = new Human(10);
console.log(`Stamina before run: ` + human.stamina);
human.run(1);
console.log(`Stamina after run: ` + human.stamina);
console.log(`Stamina before rest: ` + human.stamina);
human.rest(12);
console.log(`Stamina after rest: ` + human.stamina);
console.log(`Stamina before run: ` + human.stamina);
human.run(20);
console.log(`Stamina after run: ` + human.stamina);
// Stamina before run: 10
// Stamina after run: 9
// Stamina before rest: 9
// Stamina after rest: 21
// Stamina before run: 21
// Stamina after run: 1

The above code looks good but soon the file would be cluttered with boilerplates.

Code with Decorators

class Human {
    stamina: number;
    constructor(stamina: number) {
        this.stamina = stamina;
    }

    @log
    run(requiredStamina: number): number {
        return (this.stamina -= requiredStamina);
    }

    @log
    rest(addedStamina: number): number {
        return (this.stamina += addedStamina);
    }
}
function log(
    target: Object,
    propertyKey: string,
    descriptor: TypedPropertyDescriptor<any>
) {
    const originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in
    // order to use the correct value of `this` in this method (see notes below)
    descriptor.value = function (...args: any[]) {
        // pre
        console.log(`Stamina before ${propertyKey}: ` + args);
        // run and store result
        const result = originalMethod.apply(this, args);
        // post
        console.log(`Stamina after ${propertyKey}: ` + result);
        // return the result of the original method (or modify it before returning)
        return result;
    };

    return descriptor;
}

const human = new Human(10);
human.run(1);
human.rest(12);
human.run(20);
// Stamina before run: 1
// Stamina after run: 9
// Stamina before rest: 12
// Stamina after rest: 21
// Stamina before run: 20
// Stamina after run: 1

I have added comments in the snippet explaining the flow of data in the decorator! Simply put, decorators are invoked before the function is invoked and also has access to the arguments and the return value which could be used by the decorator.

There an N use cases for decorators in general. Let me know some interesting use case in the comment or reach out to me at radnerus93.