TypeScript: Decorators

TypeScript: Decorators
TypeScript: Decorators

Decorators can be attached to class declaration, method, accessor, property or parameter.

The syntax used to create decorator is “@expression”, where expression is a function that is called at runtime with information about the decorated declaration.

Configurations

TypeScript decorator is disabled by default. To enable experimental support for decorators, you must enable the experimentalDecorators compiler option either on the command line or in your tsconfig.json:

Command Line:

tsc --target ES5 --experimentalDecorators

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}
NOTE Decorators are an experimental feature that may change in future releases.

To create a custom decorator, you need to write a decorator factory — is a simple a function that returns another function (the expression) that will be called by the decorator at runtime.

function color(value: string) {
  // this is the decorator factory, it sets up
  // the returned decorator function
  return function (target) {
    // this is the decorator
    // do something with 'target' and 'value'...
  };
}

In TypeScript, there are four types of decorators:

  1. Class decorators: applied to the class declaration and can modify or replace the class constructor.
  2. Property decorators: applied to a property declaration within a class and can modify the behavior of the property.
  3. Method decorators: applied to a method declaration within a class and can modify the behavior of the method.
  4. Parameter decorators: applied to a parameter declaration within a function or method and can modify the behavior of the parameter.

These four types of decorators provide a flexible and powerful way to modify the behavior of classes, properties, methods, and parameters in TypeScript. By applying decorators, developers can add new features to their code, such as validation, logging, and caching, without having to modify the original code.

Class Decorators

A class decorator can modify the behavior of the class declaration in various ways. It can add new properties or methods to the class, it can modify existing properties or methods, or it can completely replace the class declaration with a new one.

function addProperty(target: any) {
  target.newProperty = "This is a new property!";
}

@addProperty
class MyClass {
  constructor() {}
}

console.log(MyClass.newProperty); // "This is a new property!"

In this example, the addProperty decorator function is applied to the MyClass class. When the code is executed, the addProperty function is called with the MyClass constructor function as its target. The addProperty function then adds a new property to the MyClass constructor function.

Class decorators can also be used to modify existing properties or methods of the class, or to completely replace the class declaration with a new one. This makes class decorators a powerful feature of TypeScript that can be used to implement a wide range of functionality.

Method Decorators

A method decorator can modify the behavior of the method declaration in various ways. It can add additional functionality to the method, it can modify the method’s arguments or return value, or it can completely replace the method with a new implementation.

Here’s an example of a method decorator that logs information about a method call:

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

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

  return descriptor;
}

class MyClass {
  @logMethod
  myMethod(arg1: string, arg2: number) {
    console.log(`myMethod(${arg1}, ${arg2}) is called.`);
  }
}

const obj = new MyClass();
obj.myMethod("hello", 42);

In this example, the logMethod decorator function is applied to the myMethod method of the MyClass class. When the code is executed, the logMethod function is called with information about the method declaration, including the target object (MyClass.prototype), the method name ("myMethod"), and a descriptor object that contains information about the method.

The logMethod function modifies the behavior of the myMethod method by wrapping the original method implementation with additional logging functionality. The modified method implementation logs information about the method call, including the method name and its arguments, before calling the original implementation.

Method decorators can be used to implement a wide range of functionality, including validation, caching, profiling, and more. They provide a flexible and reusable way to modify the behavior of individual methods within a class.

Accessor Decorators

An accessor decorator can modify the behavior of the getter or setter method in various ways. It can add additional functionality to the method, it can modify the method’s arguments or return value, or it can completely replace the method with a new implementation.

function logAccess(target: any, key: string, descriptor: PropertyDescriptor) {
  const getter = descriptor.get;
  const setter = descriptor.set;

  if (getter) {
    descriptor.get = function() {
      console.log(`Getting property ${key}`);
      return getter.call(this);
    };
  }

  if (setter) {
    descriptor.set = function(value: any) {
      console.log(`Setting property ${key} to ${value}`);
      setter.call(this, value);
    };
  }

  return descriptor;
}

class MyClass {
  private _myProperty: string = "";

  @logAccess
  get myProperty() {
    return this._myProperty;
  }

  @logAccess
  set myProperty(value: string) {
    this._myProperty = value;
  }
}

const obj = new MyClass();
obj.myProperty = "hello";
console.log(obj.myProperty);

In this example, the logAccess decorator function is applied to the myProperty getter and setter methods of the MyClass class. When the code is executed, the logAccess function is called with information about the accessor declaration, including the target object (MyClass.prototype), the accessor name ("myProperty"), and a descriptor object that contains information about the accessor.

The logAccess function modifies the behavior of the getter and setter methods by wrapping the original implementations with additional logging functionality. The modified implementations log information about property access before calling the original implementations.

Accessor decorators can be used to implement a wide range of functionality, including validation, caching, and more. They provide a flexible and reusable way to modify the behavior of individual accessors within a class.

Property Decorators

A property decorator can modify the behavior of the property in various ways. It can add additional functionality to the property, it can modify the property’s type or visibility, or it can completely replace the property with a new implementation.

Here’s an example of a property decorator that adds a new property to a class:

function addProperty(target: any, key: string) {
  Object.defineProperty(target, key + "_new", {
    value: "This is a new property!",
    enumerable: false,
    configurable: true
  });
}

class MyClass {
  @addProperty
  myProperty: string = "hello";
}

const obj = new MyClass();
console.log(obj["myProperty_new"]); // "This is a new property!"

In this example, the addProperty decorator function is applied to the myProperty property of the MyClass class. When the code is executed, the addProperty function is called with information about the property declaration, including the target object (MyClass.prototype) and the property name ("myProperty").

The addProperty function modifies the behavior of the property by adding a new property to the class prototype object using the Object.defineProperty method. The new property is named by appending "_new" to the original property name and has a fixed value of "This is a new property!".

Property decorators can be used to implement a wide range of functionality, including validation, caching, and more. They provide a flexible and reusable way to modify the behavior of individual properties within a class.

Parameter Decorators

A parameter decorator can modify the behavior of a function or method by adding additional metadata to the function signature. It can modify the parameter’s type or default value, or it can add additional information about the parameter that can be used at runtime.

Here’s an example of a parameter decorator that logs information about a function call:

function logParameter(target: any, methodName: string, parameterIndex: number) {
  console.log(`Logging parameter ${parameterIndex} of ${methodName}`);
}

class MyClass {
  myMethod(@logParameter arg1: string, @logParameter arg2: number) {
    console.log(`arg1: ${arg1}, arg2: ${arg2}`);
  }
}

const obj = new MyClass();
obj.myMethod("hello", 123);

In this example, the logParameter decorator function is applied to each parameter of the myMethod method of the MyClass class. When the code is executed, the logParameter function is called with information about the parameter declaration, including the target object (MyClass.prototype), the method name ("myMethod"), and the parameter index (0 or 1).

The logParameter function logs information about the parameter to the console, which can be used for debugging or tracing purposes. In this case, the function logs the parameter index and method name.

Parameter decorators can be used to implement a wide range of functionality, including validation, logging, and more. They provide a flexible and reusable way to modify the behavior of individual parameters within a function or method.

Thanks for reading — I hope you found this article useful. Happy coding! :)

source: https://www.typescriptlang.org/docs/handbook/decorators.html