This translation is from the “Decorators” chapter in the TypeScript Handbook.

With the introduction of classes in TypeScript and ES6, there are some scenarios where we need additional features to support annotation or modification of classes and their members.

Decorators provide a way for us to add annotations to class declarations and members through metaprogramming syntax.

Note that decorators are an experimental feature and may change in future releases.

To enable the experimental decorator feature, you must enable the experimentalDecorators compiler option either on the command line or in tsconfig.json:

tsconfig.json

{
    "compilerOptions": {
        "target": "ES5"."experimentalDecorators": true}}Copy the code

A decorator

The _ decorator _ is a special type of declaration that can be attached to a class declaration, method, accessor, property, or parameter.

Decorators use the form @expression, which must be evaluated as a function,

It is called at run time, with the embellished declaration information passed in as a parameter.

Class decorator

_ Class decorator _ declared before the class declaration (next to the class declaration)

Class decorators are applied to class constructors and can be used to monitor, modify, or replace class definitions. Class decorators cannot be used in declaration files (.d.ts) or in any external context (such as a class for declare).

function classDecorator<T extends {new(... args:any[]) : {}} > (constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override"; }}@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m; }}const gretter = new Greeter("world")
console.log(gretter.hello) // => override
Copy the code

Method decorator

_ method decorator _ Declaration before a method declaration (right next to the method declaration)

It is applied to the _ property descriptor of a method and can be used to monitor, modify, or replace method definitions.

Method decorators cannot be used in declaration files (.d.ts), overloads, or any external context (such as a class of Declare).

function enumerable(value: boolean) {
  	/* Target === is a class constructor for static members, and a prototype object for instance members. Key === Member name descriptor === member attribute descriptor (only valid if target is greater than ES5). It is used as a property descriptor for the method (only valid if target is greater than ES5) */
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log('target', target)
        console.log('key', propertyKey)
        console.log('descriptor', descriptor)
        descriptor.enumerable = value;
    };
}

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

  	// When calling the method decorator, call enumerable and pass it false -- the return value is the true decorator
    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting; }}Copy the code

Accessor decorator

Accessor decorator _ Declaration before an accessor declaration (next to accessor declaration)

Accessor decorators are applied to the accessor’s _ property descriptor and can be used to monitor, modify, or replace the definition of an accessor

Accessor decorators cannot be used in declaration files (.d.ts), or in any external context (such as a class of Declare)

Accessor decorator expressions are called as functions at run time, passing in the same arguments as the method decorator,

If the accessor decorator also returns a value, its effect is the same as that of the method decorator

Note that TypeScript does not allow you to decorate both get and set accessors for a member.

Instead, all decorators of a member must be applied to the first accessor in the document order.

This is because when the decorator is applied to a _ property descriptor, it combines the GET and set accessors rather than declaring them separately.

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

class Point {
    private _x: number;
    private _y: number;
  
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }}Copy the code

Attribute decorator

_ Property decorator _ Declaration before (immediately adjacent to) a property declaration.

Property decorators cannot be used in declaration files (.d.ts), or in any external context (such as a class of Declare).

The property decorator expression is called as a function at run time, passing in the following two arguments:

  1. Constructor of the class for static members and prototype object for instance members.
  2. Member’s name.
import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    Property decorators are called when the class is defined
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
    @format("Hello, %s")
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        let formatString = getFormat(this."greeting");
        return formatString.replace("%s".this.greeting); }}Copy the code

Parameter decorator

Parameter decorator _ declares before (immediately after) a parameter declaration. Parameter decorators are applied to class constructors or method declarations. Parameter decorators cannot be used in declaration files (.d.ts), overloads, or other external contexts (such as classes of Declare).

The parameter decorator expression is called as a function at run time, passing in the following three arguments:

  1. Constructor of the class for static members and prototype object for instance members.
  2. Member’s name.
  3. The index of a parameter in the function argument list.

The return value of the parameter decorator is ignored.

class Greeter {
  greeting: string;

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

  @validate
  greet(@required name: string) {
    return "Hello " + name + "," + this.greeting; }}Copy the code
import "reflect-metadata";

const requiredMetadataKey = Symbol("required");

// the @required decorator adds a metadata entity to mark the parameters as required
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

// @validate
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
    let method = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument."); }}}return method.apply(this.arguments); }}Copy the code