How to Use Decorators in TypeScript

Decorators in TypeScript are a powerful feature that enables developers to add extra functionality to classes, methods, properties, and parameters. They provide a way to modify the behavior of existing code without changing its actual structure. This guide will explain how to use decorators in TypeScript with easy-to-follow examples.

What are Decorators?

Decorators are special functions that can be applied to classes, methods, properties, or parameters. They are invoked at runtime and allow developers to annotate and modify code in a declarative way. To enable decorators in a TypeScript project, the experimentalDecorators flag must be set to true in the tsconfig.json file.

Enabling Decorators in TypeScript

To use decorators, the TypeScript compiler must be configured to recognize them. This can be done by setting the experimentalDecorators flag to true in the tsconfig.json file.

{
  "compilerOptions": {
    "target": "ES6",
    "experimentalDecorators": true
  }
}

Once decorators are enabled, they can be used throughout the project.

Creating a Class Decorator

A class decorator is applied to a class declaration and can be used to modify or replace a class definition. Class decorators are declared just above the class they decorate, using the @ symbol.

function LogClass(target: Function) {
  console.log(`Class ${target.name} is created.`);
}

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

const person = new Person('Alice');

In this example, the LogClass decorator logs a message when the Person class is created. The decorator is defined as a function that takes a single argument: the constructor of the class being decorated.

Method Decorators

Method decorators are applied to methods within a class. They allow developers to intercept method calls, modify their behavior, or perform additional operations before or after the method is executed.

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

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

  return descriptor;
}

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

const calc = new Calculator();
calc.add(2, 3);

Here, the LogMethod decorator logs the method name and its arguments whenever the add method is called. It wraps the original method in a new function that performs the logging before delegating to the original method.

Property Decorators

Property decorators are used to observe or modify the behavior of class properties. Unlike method decorators, they do not have access to the property value itself, but they can add metadata to properties.

function ReadOnly(target: Object, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false
  });
}

class Book {
  @ReadOnly
  title: string = 'TypeScript Guide';
}

const myBook = new Book();
myBook.title = 'New Title'; // This will cause an error in strict mode

In this example, the ReadOnly decorator is applied to the title property of the Book class, making it read-only by setting writable to false.

Parameter Decorators

Parameter decorators are used to annotate or modify method parameters. They receive three arguments: the target object, the method name, and the parameter index.

function LogParameter(target: Object, propertyKey: string, parameterIndex: number) {
  console.log(`Parameter in position ${parameterIndex} at ${propertyKey} method is decorated.`);
}

class UserService {
  greet(@LogParameter message: string): void {
    console.log(message);
  }
}

const userService = new UserService();
userService.greet('Hello, TypeScript!');

In this example, the LogParameter decorator is applied to the message parameter of the greet method in the UserService class. The decorator logs information about the parameter being decorated.

Conclusion

Decorators in TypeScript offer a powerful way to enhance code functionality without altering its structure. By leveraging class, method, property, and parameter decorators, developers can easily add reusable functionality across their projects. With the examples provided in this guide, it's easy to get started with using decorators in TypeScript.