TypeScript Decorators

Decorators are an experimental feature of TypeScript.

They allow developers to add additional functionality to classes, methods, properties, or parameters without modifying the original class.

A decorator is essentially a function that can be called at runtime to modify the behavior of the target object.

SVG Diagram: Decorator Types Background Title Decorator Types and Application Locations Center: Class @ClassDecorator Class Decorator Arrows to all directions Top: Property Decorator Property Decorator @propertyDecorator Right: Method Decorator Method Decorator @methodDecorator Bottom: Parameter Decorator Parameter Decorator @paramDecorator Lower Right: Accessor Decorator Accessor Decorator @getterDecorator Decorator Factory Explanation Decorator Factory A decorator factory is a function that returns a decorator function, enabling configuration through parameters. Example: function color(code: string) { return function(target) { ... }; } → @color("34") Arrow marker

Introduction to Decorators

Decorators use the `@` symbol as syntactic sugar and can be attached to classes, methods, accessors, properties, or parameters.

This pattern is commonly used in framework development; for example, Angular, TypeORM, etc., extensively use decorators to implement dependency injection, data validation, and other features.

Note:Decorators are currently an experimental feature and need to be explicitly enabled in tsconfig.json. When using them in production, please confirm the project's level of support for experimental features.


Configuration to Enable Decorators

Before using decorators, you need to enable the relevant compiler options in the TypeScript configuration file tsconfig.json.

tsconfig.json Configuration

{
    "compilerOptions": {
        // Enable decorator syntax
        "experimentalDecorators": true,

        // Enable decorator metadata (used for dependency injection frameworks)
        "emitDecoratorMetadata": true
    }
}

Parameter description:

  • experimentalDecorators:Enables decorator syntax support; this is a prerequisite for using decorators.
  • emitDecoratorMetadata:Generates decorator metadata in the compiled JavaScript for use by dependency injection frameworks.

Class Decorators

Class decorators are applied to the class constructor and can modify the class definition or add additional processing logic.

A class decorator receives one parameter: the constructor of the target class.

Basic Usage of Class Decorators

// Define a class decorator function
// The parameter target is the decorated class constructor
function sealed(target: Function) {
    // Print the name of the class the decorator is applied to
    console.log("Decorator applied to: " + target.name);

    // Use Object.seal to lock the constructor and prototype
    // Prevent adding or removing properties at runtime
    Object.seal(target);
    Object.seal(target.prototype);
}

// Use the @ syntax to apply the decorator to the class
@sealed
class Person {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

// Create an instance for testing
var person = new Person("EXAMPLE");
console.log("Created: " + person.name);

// Try to add a new property (will be prevented because the class is sealed)
// person.age = 25; // silently fails

Output:

装饰器 applied to: Person
创建: EXAMPLE

Explanation:

Class decorators execute at class definition time and are typically used to modify class behavior, add metadata, or implement AOP (Aspect-Oriented Programming).


Method Decorators

Method decorators are applied to class methods and can modify the method's Property Descriptor.

A method decorator receives three parameters: the target object, the property name, and the property descriptor.

Basic Usage of Method Decorators

// Define a method decorator factory
// Return a decorator function
function enumerable(value: boolean) {
    // Return a decorator function that receives three parameters
    return function (
        target: any,           // The prototype object of the containing class
        propertyKey: string,    // The method name
        descriptor: PropertyDescriptor // The property descriptor
    ) {
        // Modify the enumerable attribute of the property
        // false means the method is not enumerable
        descriptor.enumerable = value;
    };
}

class Greeter {
    greeting: string;

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

    // Apply the decorator to set the method as non-enumerable
    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}

var g = new Greeter("World");

// Check whether the greet method is enumerable
console.log("Method enumerable: " + g.propertyIsEnumerable("greet"));

// Iterate over the object's properties
for (var key in g) {
    console.log("Properties: " + key);
}

Output:

方法可枚举: false

Tip:PropertyDescriptor contains attributes such as enumerable, configurable, writable, and value, which can be modified as needed.


Accessor Decorators

Accessor decorators are applied to getter and setter methods of a class.

Similar to method decorators, accessor decorators can also modify the property descriptor.

Usage of Accessor Decorators

// Accessor decorator factory
function configurable(value: boolean) {
    return function (
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        // Modify the configurable attribute of the property
        // false means the accessor cannot be reconfigured or deleted
        descriptor.configurable = value;
    };
}

class Point {
    private _x: number = 0;
    private _y: number = 0;

    // Use the decorator to lock the getter
    @configurable(false)
    get x() {
        return this._x;
    }

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

    set x(value: number) {
        this._x = value;
    }

    set y(value: number) {
        this._y = value;
    }
}

var point = new Point();
point.x = 10;
point.y = 20;
console.log("Coordinates: (" + point.x + ", " + point.y + ")");

Explanation:

Accessor decorators cannot be applied to both the getter and setter of the same property at the same time; only one of them can be chosen.


Property Decorators

Property decorators are applied to property definitions of a class.

A property decorator receives two parameters: the target object and the property name.

Usage of Property Decorators

// Property decorator factory
function format(formatString: string) {
    return function (
        target: any,           // The prototype object of the class
        propertyKey: string     // The property name
    ) {
        // Store metadata on the target object
        // Use propertyKey + "_format" as the key name to avoid conflicts
        Object.defineProperty(target, propertyKey + "_format", {
            value: formatString,
            writable: false,
            enumerable: false,
            configurable: true
        });
    };
}

class User {
    // Apply the property decorator to specify the date format
    @format("YYYY-MM-DD")
    birthDate: string;

    constructor(birthDate: string) {
        this.birthDate = birthDate;
    }
}

var user = new User("1990-01-01");
console.log("Date of birth: " + user.birthDate);

// Access the stored metadata
console.log("Date format: " + (user as any).birthDate_format);

Output:

出生日期: 1990-01-01
日期格式: YYYY-MM-DD

Parameter Decorators

Parameter decorators are applied to parameters of class methods and can add metadata or markers to parameters.

A parameter decorator receives three parameters: the target object, the method name, and the index of the parameter in the function.

Usage of Parameter Decorators

// Parameter decorator
// Used to record parameter information or perform validation
function logParameter(
    target: any,               // The prototype object of the class
    propertyKey: string,       // The method name
    parameterIndex: number    // The index position of the parameter in the function (starting from 0)
) {
    console.log("Parameter decorator: " + propertyKey +
        " " + (parameterIndex + 1) + "th parameter");
}

class Greeter {
    greeting: string;

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

    // Use the @ syntax before the parameter to apply the decorator
    greet(@logParameter name: string) {
        return this.greeting + ", " + name;
    }
}

var greeter = new Greeter("Hello");
greeter.greet("EXAMPLE");

Output:

参数装饰器: greet 第 1 个参数

Decorator Factory

A decorator factory is a function that returns a decorator function.

Through a decorator factory, you can pass custom parameters when applying the decorator, enabling more flexible configuration.

Decorator Factory for Colored Logs

// Decorator factory: receives configuration parameters and returns a decorator function
function color(colorCode: string) {
    // colorCode is the color code in ANSI escape sequences
    // For example: 34 = blue, 31 = red, 32 = green
    return function (
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        // Save the original method
        var originalMethod = descriptor.value;

        // Override the method to add color
        descriptor.value = function (...args: any[]) {
            // Call the original method to get the return value
            var result = originalMethod.apply(this, args);

            // If in a terminal environment, add color to the output
            // ANSI escape sequence format: \x1b[color code m content \x1b[0m
            return "\x1b[" + colorCode + "m" + result + "\x1b[0m";
        };
    };
}

class Logger {
    // Use decorator factory, pass in blue color code 34
    @color("34")
    log(message: string): string {
        return message;
    }

    @color("31")
    error(message: string): string {
        return message;
    }

    @color("32")
    success(message: string): string {
        return message;
    }
}

var logger = new Logger();
console.log(logger.log("This is a blue log"));
console.log(logger.error("This is a red error"));
console.log(logger.success("This is a green success"));

Run result:

This is blue log (displayed as blue in terminal)
This is red error (displayed as red in terminal)
This is green success (displayed as green in terminal)

Explanation:The decorator factory is the most commonly used form in actual development. It allows passing parameters when applying decorators, enabling configuration.


Execution Order of Decorators

When a class has multiple decorators, the execution order follows specific rules.

  • Decorators are applied from bottom to top
  • Multiple decorators of the same type execute from right to left
  • Parameter decorators execute before method decorators

Example of Decorator Execution Order

// Stack multiple decorators
function first() {
    console.log("first decorator");
    return function (target: any) {
        console.log("first decorator function");
    };
}

function second() {
    console.log("second decorator");
    return function (target: any) {
        console.log("second decorator function");
    };
}

@first()
@second()
class MyClass {
    name: string;
}

var obj = new MyClass();

Run result:

second 装饰器
first 装饰器
second 装饰器函数
first 装饰器函数

Explanation:

The decorator function definitions execute first (console.log), then the decorator functions execute in order from bottom to top.


Real-world Application Scenarios

Decorators have a wide range of application scenarios in real projects.

Logging

Automatically record method call logs.

Example

// Logging decorator
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    var originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
        console.log("Calling method: " + propertyKey + ", parameters: " + JSON.stringify(args));
        var result = originalMethod.apply(this, args);
        console.log("Method returned: " + JSON.stringify(result));
        return result;
    };
}

class MathService {
    @log
    add(a: number, b: number): number {
        return a + b;
    }

    @log
    multiply(a: number, b: number): number {
        return a * b;
    }
}

var math = new MathService();
console.log("Calculation result: " + math.add(5, 3));

Run result:

调用方法: add,参数: [5,3]
方法返回: 8
计算结果: 8

Permission Verification

Implement method-level permission checking.

Example

// Simulate current user role
var currentUser = { role: "admin" };

// Permission decorator
function requireRole(role: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        var originalMethod = descriptor.value;

        descriptor.value = function (...args: any[]) {
            if (currentUser.role !== role) {
                console.log("Insufficient permissions, cannot execute " + propertyKey);
                return null;
            }
            return originalMethod.apply(this, args);
        };
    };
}

class AdminService {
    @requireRole("admin")
    deleteUser(id: number): string {
        return "Delete user " + id + " succeeded";
    }

    @requireRole("admin")
    viewUser(id: number): string {
        return "View user " + id;
    }
}

var admin = new AdminService();
console.log(admin.viewUser(1));
console.log(admin.deleteUser(1));

// Simulate regular user
currentUser = { role: "user" };
console.log(admin.deleteUser(2));

Run result:

查看用户 1
删除用户 1 成功
权限不足,无法执行 deleteUser

Notes

  • Experimental feature:Decorators are a _stage 3_ proposal of ECMAScript and are currently still an experimental feature
  • Compiler options:Must enable experimentalDecorators
  • Type definitions:A newer version of TypeScript is required for full type support
  • Debugging note:Decorators execute at compile time, and some debugging tools may not correctly map source locations

Recommendation:When using decorators in a project, it is recommended to create dedicated decorator utility classes or function libraries to uniformly manage the definition and usage of decorators.


Summary

TypeScript decorators provide a powerful metaprogramming capability.

  • Class decorators:Modify the class itself, can be used to add metadata, lock classes, etc.
  • Method decorators:Modify method properties, can be used for logging, validation, etc.
  • Accessor decorators:Modify getter/setter, control the configurability of properties
  • Property decorators:Add metadata to properties
  • Parameter decorators:Mark or validate method parameters
  • Decorator factory:Achieve more flexible decorator configuration through parameterization

Other extensions