Decorators in TypeScript

Learn about Decorators in TypeScript with this detailed tutorial. Includes explanations and examples to help you master decorators.

Introduction

Decorators are a special kind of declaration that can be attached to a class, method, accessor, property, or parameter. Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature in TypeScript. Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members.

Enabling Experimental Decorators

To use decorators in TypeScript, you need to enable the experimentalDecorators compiler option in your tsconfig.json file.

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

Class Decorators

A class decorator is a function that takes a class constructor and returns a new constructor or modifies the existing one.

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

@sealed
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

Explanation

In this example, the sealed decorator seals the constructor and its prototype, preventing new properties from being added.

Method Decorators

A method decorator is a function that takes three arguments: the target (either the constructor or prototype), the property key, and the property descriptor.

function enumerable(value: boolean) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return `Hello, ${this.greeting}`;
    }
}

Explanation

In this example, the enumerable decorator sets the enumerable property of the greet method to false.

Accessor Decorators

An accessor decorator is similar to a method decorator but is applied to an accessor (getter or setter).

function configurable(value: boolean) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}

class Point {
    private _x: number;
    private _y: number;

    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() {
        return this._x;
    }

    @configurable(false)
    get y() {
        return this._y;
    }
}

Explanation

In this example, the configurable decorator sets the configurable property of the x and y accessors to false.

Property Decorators

A property decorator is a function that takes two arguments: the target (either the constructor or prototype) and the property key.

function format(formatString: string) {
    return function(target: any, propertyKey: string) {
        let value: string;

        const getter = () => value;
        const setter = (newValue: string) => {
            value = `${formatString} ${newValue}`;
        };

        Object.defineProperty(target, propertyKey, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    };
}

class Greeter {
    @format("Hello")
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }
}

let greeter = new Greeter("world");
console.log(greeter.greeting); // Hello world

Explanation

In this example, the format decorator formats the value of the greeting property by prepending the format string to it.

Parameter Decorators

A parameter decorator is a function that takes three arguments: the target (either the constructor or prototype), the property key, and the parameter index.

function required(target: any, propertyKey: string, parameterIndex: number) {
    console.log(`Parameter at index ${parameterIndex} in ${propertyKey} is required`);
}

class Greeter {
    greet(@required message: string) {
        return `Hello, ${message}`;
    }
}

Explanation

In this example, the required decorator logs the parameter index and property key of the decorated parameter.

Conclusion

Decorators in TypeScript provide a powerful way to add metadata and modify the behavior of classes, methods, accessors, properties, and parameters. By understanding and using decorators, you can write more expressive and maintainable code.