Diverse Examples of TypeScript Decorators

Explore practical examples of TypeScript decorators to enhance your programming skills.
By Jamie

Understanding TypeScript Decorators

TypeScript decorators are a powerful feature that enables developers to add metadata and modify the behavior of classes, methods, properties, or parameters. They are essentially functions that are prefixed with the @ symbol and are used to annotate and modify the elements they are applied to. In this article, we will explore three diverse examples of TypeScript decorators, providing practical use cases that can enhance your coding practices.

Example 1: Class Decorator for Logging

Use Case

Imagine you want to track the instantiation of classes for debugging purposes or performance monitoring. A class decorator can help you log each time a class is created.

function LogClass(target: Function) {
    const original = target;
    const newConstructor: any = function (...args: any[]) {
        console.log(`Creating instance of: ${original.name}`);
        return new original(...args);
    };
    newConstructor.prototype = original.prototype;
    return newConstructor;
}

@LogClass
class User {
    constructor(public name: string) {}
}

const user1 = new User('Alice'); // Logs: Creating instance of: User

Notes

  • You can enhance the logging functionality by including timestamps or more context.
  • This pattern can be useful in larger applications where knowing when instances of certain classes are created is crucial.

Example 2: Method Decorator for Timing Execution

Use Case

A method decorator can be used to measure the execution time of specific methods, which is particularly useful for performance optimization or tracking the efficiency of your code.

function LogExecutionTime(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        const start = performance.now();
        const result = originalMethod.apply(this, args);
        const end = performance.now();
        console.log(`Execution time of ${propertyName}: ${end - start} ms`);
        return result;
    };
}

class Calculator {
    @LogExecutionTime
    add(a: number, b: number) {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(5, 10); // Logs execution time of add method

Notes

  • This can easily be extended to log execution times of all methods within a class.
  • Consider integrating this with a reporting tool to keep track of performance metrics across your application.

Example 3: Property Decorator for Validation

Use Case

Property decorators can be used to enforce validation rules. For instance, you might want to ensure that a property is a string and not empty in a class.

function IsString(target: any, propertyName: string) {
    let value: string;
    const getter = () => value;
    const setter = (newValue: string) => {
        if (typeof newValue !== 'string' || newValue.trim() === '') {
            throw new Error(`Invalid value for ${propertyName}. It must be a non-empty string.`);
        }
        value = newValue;
    };
    Object.defineProperty(target, propertyName, { get: getter, set: setter });
}

class Product {
    @IsString
    name: string;
}

const product = new Product();
product.name = 'Laptop'; // Works
product.name = ''; // Throws: Invalid value for name. It must be a non-empty string.

Notes

  • You can create more sophisticated validation logic by chaining multiple decorators together.
  • This approach can significantly enhance data integrity in your applications.

By implementing these examples of TypeScript decorators, you can streamline your code, improve performance, and enforce better practices in your development process.