TypeScript Decorators

Add metadata and modify class behavior

✨ What are Decorators?

Decorators are special functions that modify classes, methods, properties, or parameters. They use the @ symbol and provide a way to add annotations and meta-programming syntax. Enable them in tsconfig.json with "experimentalDecorators": true.


// Enable in tsconfig.json: "experimentalDecorators": true
@sealed
class Person {
    @readonly
    name: string = "John";
}
                                    

Types of Decorators

🏛️

Class Decorators

Modify or observe classes

@Component
class MyComponent {
    // class code
}
⚙️

Method Decorators

Modify method behavior

class API {
    @log
    getData() {
        // method code
    }
}
🏷️

Property Decorators

Add metadata to properties

class User {
    @required
    email: string;
}
📝

Parameter Decorators

Annotate method parameters

method(@required id: number) {
    // method code
}

🔹 Class Decorator

Class decorators are applied to the class constructor:

// Decorator function
function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
    console.log(`${constructor.name} is now sealed`);
}

@sealed
class Person {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
}

const person = new Person("Alice");
console.log(person.name);  // Alice

// Try to add new property (won't work in strict mode)
// Person.prototype.age = 30;  // Error in strict mode

Output:

Person is now sealed
Alice

🔹 Method Decorator

Method decorators can modify or observe method behavior:

// Log decorator
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
        console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
        const result = originalMethod.apply(this, args);
        console.log(`Result: ${result}`);
        return result;
    };
    
    return descriptor;
}

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

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

Output:

Calling add with args: [5,3]
Result: 8
Calling multiply with args: [4,2]
Result: 8

🔹 Property Decorator

Property decorators add metadata to class properties:

// Readonly decorator
function readonly(target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
        writable: false,
        configurable: false
    });
    console.log(`${propertyKey} is now readonly`);
}

class Config {
    @readonly
    apiUrl: string = "https://api.example.com";
    
    @readonly
    version: string = "1.0.0";
}

const config = new Config();
console.log(config.apiUrl);   // https://api.example.com
console.log(config.version);  // 1.0.0

// Try to modify (won't work)
// config.apiUrl = "new-url";  // Error in strict mode

Output:

apiUrl is now readonly
version is now readonly
https://api.example.com
1.0.0

🔹 Decorator Factory

Decorator factories allow you to customize decorator behavior:

// Decorator factory with parameters
function minLength(length: number) {
    return function(target: any, propertyKey: string) {
        let value: string;
        
        const getter = () => value;
        const setter = (newVal: string) => {
            if (newVal.length < length) {
                console.log(`${propertyKey} must be at least ${length} characters`);
            } else {
                value = newVal;
            }
        };
        
        Object.defineProperty(target, propertyKey, {
            get: getter,
            set: setter
        });
    };
}

class User {
    @minLength(3)
    username: string = "";
    
    @minLength(8)
    password: string = "";
}

const user = new User();
user.username = "Jo";        // username must be at least 3 characters
user.username = "John";      // ✅ Valid
user.password = "pass";      // password must be at least 8 characters
user.password = "password123"; // ✅ Valid

Output:

username must be at least 3 characters
password must be at least 8 characters

🔹 Multiple Decorators

You can apply multiple decorators to the same target:

function first() {
    console.log("first(): factory evaluated");
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("first(): called");
    };
}

function second() {
    console.log("second(): factory evaluated");
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("second(): called");
    };
}

class Example {
    @first()
    @second()
    method() {
        console.log("method called");
    }
}

const example = new Example();
example.method();

Output:

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
method called

Note: Decorators are evaluated top-to-bottom, but executed bottom-to-top.

💡 Key Points:

  • Enable decorators with "experimentalDecorators": true in tsconfig.json
  • Decorators use the @ symbol before the target
  • Class decorators modify class constructors
  • Method decorators can wrap or modify methods
  • Property decorators add metadata to properties
  • Decorator factories allow parameterized decorators
  • Multiple decorators execute bottom-to-top

🧠 Test Your Knowledge

What symbol is used to apply a decorator in TypeScript?