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
- Keep decorators focused on a single responsibility
- Use decorator factories for configuration
- Consider performance implications
- Document decorator behavior clearly
- Test decorated components thoroughly
Practice Exercise
Create a set of decorators for a REST API service that:
- Handles route definitions
- Validates request parameters
- Manages authentication
- Logs method calls