Table of contents
    blog cover

    Exploring TypeScript Decorators

    Web Development
    Web Development
    TypeScript, a statically typed superset of JavaScript, is a modern-age programming language that offers a wide array of advanced features. One of these features is decorators, a concept that greatly extends your ability to modify classes, properties, and methods. This post aims to shed light on the concept of TypeScript decorators, with detailed examples and explanations.

    Note: To enable experimental support for decorators, you must enable the experimentalDecorators compiler option either on the command line or in your tsconfig.json.

    Command Line
    // language: bash
    tsc --target ES5 --experimentalDecorators

    tsconfig.json
    // language: javascript
    {  
      "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true  
      }
    }

    Read TypeSscript decorator document for more details

    1. What is decorator?

    Decorators are a design pattern in TypeScript, offering a way to add both annotations and a meta-programming syntax for class declarations and members. They can be used to modify or encapsulate behaviors of methods, classes, properties or parameters, without changing their source code.

    Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
    For example, given the decorator @log we might write the sealed function as follows:

    // language: javascript
    function log(target) {  
      // do something with 'target'
    }

    If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.
    We can write a decorator factory in the following fashion:

    // language: javascript
    function log(mode: string) {
      return function (target) {
        // do something with 'target' and 'mode'...  
      }
    }

    2. Class Decorators

    Class decorators in TypeScript are applied to the constructor of the class and can be used to observe, modify, or replace a class definition. These decorators are called with one parameter, which is the constructor of the decorated class.

    Here's the general syntax for a class decorator:
    // language: javascript
    function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
        return class extends constructor {
            // new behavior
        }
    }

    Example 1:
    // language: javascript
    function circleDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
        return class extends constructor {
            lower_bound = 0;
            pi = 3.14;
        }
    }
    
    @circleDecorator
    class MathTool {
        lower_bound: number;
        upper_bound = 100;
    
        constructor() {
            this.lower_bound = -100;
        }
    }
    
    console.log(new MathTool());
    // { lower_bound: 0, upper_bound: 100, pi: 3.14 }

    In this example, the circleDecorator function replaces the original MathTool with a new class that extends MathTool, adding a pi and overriding the lower_bound property. When a new instance of MathTool is created, it contains the modifications made by the decorator.

    Example 2:
    // language: javascript
    function logCreation<T extends { new (...args: any[]): {} }>(constructor: T) {
        return class extends constructor {
            constructor(...args: any[]) {
                super(...args);
                console.log(`Instance of ${constructor.name} created`);
            }
        }
    }
    
    @logCreation
    class Character {
        name: string;
    
        constructor(name: string) {
            this.name = name;
        }
    }
    
    @logCreation
    class Enemy {
        type: string;
    
        constructor(type: string) {
            this.type = type;
        }
    }
    
    const character = new Character("John") // "Instance of Character created" 
    const enemy = new Enemy("Dragon") // "Instance of Enemy created" 

    3. Method Decorators

    Method decorators in TypeScript are a powerful and flexible feature that can be used to observe, modify, or replace a method definition. They're declared just before a method declaration. The decorator is applied to the Property Descriptor for the method and can be used to observe, modify, or replace a method definition.

    A method decorator is expressed as a function that will be called at runtime with three arguments:
    1. Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
    2. The name of the member.
    3. The Property Descriptor for the member.
    // language: javascript
    function methodDecorator<T>(
      target: Object, 
      propertyKey: string | symbol, 
      descriptor: TypedPropertyDescriptor<T>
    ) {
        // your decorator logic
    }

    Here's an example of a method decorator usage:
    // language: javascript
    function deprecated(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log(`${propertyKey} has been deprecated`);
        descriptor.value = function() {
            return 999;
        }
    }
    
    class MathTool {
        @deprecated
        sum() {
            return 100;
        }
    }
    
    let myInstance = new MathTool();
    console.log(myInstance.sum());
    // "sum has been deprecated" 
    // 999

    In the above example, the @deprecated decorator replaces the original sum with a new function that indicates the method has been deprecated. The original functionality of sum is lost, replaced by the new function.
    Method decorators provide an elegant way to add behavior to methods or change their behavior entirely. They allow for cleaner, more organized, and modular code, enhancing the overall development experience with TypeScript.

    Created at 2024-04-03 22:56:46 +0700

    Related blogs