TypeScript
08 / 18

Decorators & Metadata

TypeScript Decorators & Metadata

Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. They are functions that modify classes and their members.

Class Decorators

// Simple class decorator
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

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

// Decorator factory (configurable)
function logger(prefix: string) {
  return function(constructor: Function) {
    console.log(`${prefix}: ${constructor.name}`);
  };
}

@logger('Creating')
class User {
  constructor(public name: string) {}
}

// Decorator that replaces constructor
function timestamp<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    timestamp = new Date();
  };
}

@timestamp
class Document {
  constructor(public title: string) {}
}

const doc = new Document('My Doc');
console.log((doc as any).timestamp); // Date object

Method Decorators

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

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

const calc = new Calculator();
calc.add(2, 3); // Logs: Calling add with [2, 3], Result: 5

// Performance measurement
function measure(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  
  descriptor.value = async function(...args: any[]) {
    const start = performance.now();
    const result = await original.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} took ${(end - start).toFixed(2)}ms`);
    return result;
  };
  
  return descriptor;
}

class DataProcessor {
  @measure
  async processData(data: any[]): Promise<any[]> {
    // Simulate processing
    await new Promise(resolve => setTimeout(resolve, 100));
    return data.map(x => x * 2);
  }
}

Property & Parameter Decorators

// Property decorator
function required(target: any, propertyKey: string) {
  let value: any;
  
  const getter = () => value;
  const setter = (newValue: any) => {
    if (!newValue) {
      throw new Error(`${propertyKey} is required`);
    }
    value = newValue;
  };
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class User {
  @required
  email!: string;
  
  name?: string;
}

const user = new User();
// user.email = ''; // Error: email is required
user.email = 'test@example.com'; // ✅

// Parameter decorator
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Parameter decorator: ${propertyKey}[${parameterIndex}]`);
}

class Greeter {
  greet(@logParameter message: string): void {
    console.log(message);
  }
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free