Skip to main content

Decorators in TypeScript

Decorators provide a way to add annotations and modify classes and their members. They're commonly used in frameworks like Angular and TypeORM.

Class Decorators

Modify or enhance a class definition:

// Simple class decorator
function Logger(target: Function) {
console.log(`Class ${target.name} was created`);
}

@Logger
class Example {
constructor() {
console.log('Creating instance');
}
}

Method Decorators

Add behavior to class methods:

function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Store the original method
const originalMethod = descriptor.value;

// Replace with new function
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
const result = originalMethod.apply(this, args);
console.log(`Result:`, result);
return result;
};
}

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

Property Decorators

Modify class properties:

function ValidateRange(min: number, max: number) {
return function(target: any, propertyKey: string) {
let value: number;

const getter = function() {
return value;
};

const setter = function(newVal: number) {
if (newVal < min || newVal > max) {
throw new Error(`Value must be between ${min} and ${max}`);
}
value = newVal;
};

Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
};
}

class Temperature {
@ValidateRange(-273.15, 100)
celsius: number = 0;
}

Parameter Decorators

Add metadata about method parameters:

function Required(target: any, propertyKey: string, parameterIndex: number) {
// Add metadata about required parameters
const requiredParams: number[] = Reflect.getMetadata('required', target, propertyKey) || [];
requiredParams.push(parameterIndex);
Reflect.defineMetadata('required', requiredParams, target, propertyKey);
}

class UserService {
createUser(@Required username: string, @Required email: string) {
// Implementation
}
}

Decorator Factories

Create customizable decorators:

function Timeout(milliseconds: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
setTimeout(() => {
originalMethod.apply(this, args);
}, milliseconds);
};
};
}

class Notifications {
@Timeout(1000)
showMessage(message: string) {
console.log(message);
}
}

Best Practices

  1. Keep decorators focused on a single responsibility
  2. Use decorator factories for configuration
  3. Consider performance implications
  4. Document decorator behavior clearly
  5. Test decorated components thoroughly

Practice Exercise

Create a set of decorators for a REST API service that:

  1. Handles route definitions
  2. Validates request parameters
  3. Manages authentication
  4. Logs method calls