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. They provide a way to add both annotations and a meta-programming syntax for class declarations and members.

Key Concepts

  1. Decorator Factories: Functions that return the decorator function.
  2. Decorator Composition: Multiple decorators can be applied to a single declaration.
  3. Decorator Evaluation: Decorators are evaluated in a specific order.

Types of Decorators

  1. Class Decorators: Applied to a class constructor.
  2. Method Decorators: Applied to a method.
  3. Accessor Decorators: Applied to a getter or setter.
  4. Property Decorators: Applied to a property.
  5. Parameter Decorators: Applied to a method parameter.

Class Decorators

A class decorator is a function that takes a class constructor as an argument and returns either a new constructor or the same constructor.

Example

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}`;
    }
}

In this example, the @sealed decorator seals the Greeter class, preventing new properties from being added to it.

Method Decorators

A method decorator is a function that takes three arguments: the target (either the constructor function of the class for a static method or the prototype of the class for an instance method), the name of the method, and the property descriptor.

Example

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}`;
    }
}

In this example, the @enumerable(false) decorator sets the enumerable property of the greet method's descriptor to false.

Property Decorators

A property decorator is a function that takes two arguments: the target (either the constructor function of the class for a static property or the prototype of the class for an instance property) and the name of the property.

Example

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

        const getter = function () {
            return value;
        };

        const setter = function (newVal: string) {
            value = `${formatString} ${newVal}`;
        };

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

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

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

In this example, the @format('Hello') decorator modifies the greeting property to prepend "Hello" to any value assigned to it.

Practical Exercise

Task

  1. Create a class Person with properties firstName and lastName.
  2. Add a method getFullName that returns the full name of the person.
  3. Create a decorator logMethod that logs the method name and arguments whenever getFullName is called.

Solution

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        console.log(`Method ${propertyKey} called with args: ${JSON.stringify(args)}`);
        return originalMethod.apply(this, args);
    };

    return descriptor;
}

class Person {
    firstName: string;
    lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @logMethod
    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

const person = new Person('John', 'Doe');
console.log(person.getFullName());

Explanation

  • The logMethod decorator logs the method name and arguments whenever the getFullName method is called.
  • The Person class has properties firstName and lastName, and a method getFullName that returns the full name.
  • When getFullName is called, the decorator logs the method name and arguments, then calls the original method.

Summary

  • Decorators provide a way to add annotations and meta-programming syntax to class declarations and members.
  • There are different types of decorators: class, method, accessor, property, and parameter decorators.
  • Decorators can be composed and are evaluated in a specific order.
  • Practical examples and exercises help reinforce the concepts learned.

In the next section, we will explore asynchronous programming in TypeScript, starting with Promises.

© Copyright 2024. All rights reserved