Decorators in TypeScript are a powerful feature that allows developers to modify the behavior of classes, methods, properties, and parameters. They are a form of metadata annotation that can be applied to code, providing a way to add additional information or functionality in a declarative manner. Decorators are inspired by a similar feature in languages like Python and Java, and they are widely used in frameworks such as Angular to enhance code readability and maintainability.

At their core, decorators are simply functions that are prefixed with an @ symbol and are executed at runtime. They can be used to observe, modify, or replace the definition of the target they are applied to. Decorators are applied to classes and their members, and they can be categorized into several types: class decorators, method decorators, accessor decorators, property decorators, and parameter decorators.

Class Decorators

Class decorators are applied to the constructor of a class. They can be used to modify or replace the class definition. A class decorator is a function that takes a single parameter, which is the constructor of the class being decorated.


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 is applied to the Greeter class. The sealed function seals the constructor and its prototype, preventing new properties from being added to the class.

Method Decorators

Method decorators are applied to methods within a class. They can be used to modify the method's behavior. A method decorator is a function that takes three arguments: the target object, the name of the method, and the property descriptor of the method.


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

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

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

In this example, the @enumerable(false) decorator is applied to the greet method of the Person class. The decorator modifies the enumerable property of the method's descriptor, making it non-enumerable.

Accessor Decorators

Accessor decorators are applied to getters and setters within a class. They are similar to method decorators and can be used to modify the behavior of the accessor. An accessor decorator takes the same three arguments as a method decorator.


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

class Car {
  private _speed: number = 0;

  @configurable(false)
  get speed() {
    return this._speed;
  }

  set speed(value: number) {
    this._speed = value;
  }
}

Here, the @configurable(false) decorator is applied to the speed getter of the Car class, making the property non-configurable.

Property Decorators

Property decorators are applied to properties within a class. They are used to observe or modify the property definition. A property decorator is a function that takes two arguments: the target object and the name of the property.


function logProperty(target: any, key: string) {
  let value = target[key];

  const getter = () => {
    console.log(`Get: ${key} => ${value}`);
    return value;
  };
  const setter = (newVal) => {
    console.log(`Set: ${key} => ${newVal}`);
    value = newVal;
  };

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

class Product {
  @logProperty
  price: number;
}

In this example, the @logProperty decorator is applied to the price property of the Product class. The decorator logs access to the property, providing insight into when the property is read or modified.

Parameter Decorators

Parameter decorators are applied to the parameters of a method. They can be used to modify the behavior of the parameter. A parameter decorator is a function that takes three arguments: the target object, the name of the method, and the index of the parameter in the method's parameter list.


function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  const metadataKey = `log_${propertyKey}_parameters`;
  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(parameterIndex);
  } else {
    target[metadataKey] = [parameterIndex];
  }
}

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

In this example, the @logParameter decorator is applied to the first parameter of the add method in the Calculator class. The decorator logs the index of the parameter, allowing for additional behavior to be implemented based on the parameter's usage.

Decorator Factories

Decorator factories are functions that return a decorator function. They allow for the creation of configurable decorators, providing more flexibility in their implementation. Decorator factories are commonly used to pass arguments to decorators.


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

    descriptor.value = function(...args: any[]) {
      console.log(`${message}: ${args}`);
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class Logger {
  @log('Executing method')
  execute(task: string) {
    console.log(`Task: ${task}`);
  }
}

In this example, the @log('Executing method') decorator factory is used to create a decorator that logs a custom message before executing the execute method in the Logger class.

Use Cases and Best Practices

Decorators are widely used in modern TypeScript applications, especially in frameworks like Angular. They provide a clean and expressive way to add functionality to classes and their members. Some common use cases for decorators include:

  • Logging: Decorators can be used to log method calls, parameter values, and property access.
  • Validation: Decorators can enforce validation rules on method parameters or class properties.
  • Authorization: Decorators can restrict access to certain methods based on user roles or permissions.
  • Caching: Decorators can cache method results to improve performance.

When using decorators, it's important to follow best practices to maintain code quality and readability:

  • Keep decorators simple: Avoid adding too much logic to decorators. They should be focused on a single responsibility.
  • Use decorator factories: When a decorator requires configuration, use a factory to pass arguments.
  • Document decorators: Provide clear documentation for decorators to explain their purpose and usage.
  • Test decorators: Ensure that decorators are thoroughly tested to avoid unexpected behavior in the application.

In conclusion, decorators are a versatile feature in TypeScript that enable developers to enhance the functionality of their code in a declarative and reusable manner. By understanding the different types of decorators and their use cases, developers can leverage this feature to build more robust and maintainable applications.

Now answer the exercise about the content:

What is the primary purpose of decorators in TypeScript?

You are right! Congratulations, now go to the next page

You missed! Try again.

Article image Exploring TypeScript Utility Types

Next page of the Free Ebook:

72Exploring TypeScript Utility Types

10 minutes

Obtenez votre certificat pour ce cours gratuitement ! en téléchargeant lapplication Cursa et en lisant lebook qui sy trouve. Disponible sur Google Play ou App Store !

Get it on Google Play Get it on App Store

+ 6.5 million
students

Free and Valid
Certificate with QR Code

48 thousand free
exercises

4.8/5 rating in
app stores

Free courses in
video, audio and text