Decorators provide a way to add annotations and metadata to classes, methods, and properties.
Enabling Decorators
Enable in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Class Decorators
Applied to class constructors.
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
Decorator Factory
Returns a decorator function.
function color(value: string) {
return function (constructor: Function) {
// Do something with constructor and value
};
}
@color("red")
class Car {}
Method Decorators
Applied to method descriptors.
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
Property Decorators
Applied to class properties.
function format(formatString: string) {
return function (target: any, propertyKey: string) {
// Store metadata about the property
};
}
class User {
@format("uppercase")
name: string;
}
Parameter Decorators
Applied to function parameters.
function required(target: any, propertyKey: string, parameterIndex: number) {
// Mark parameter as required
}
class UserService {
createUser(@required name: string, age: number) {
// ...
}
}
Accessor Decorators
Applied to getters and setters.
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
@configurable(false)
get x() {
return this._x;
}
}
Common Decorator Patterns
Logging Decorator
function log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyName} with`, args);
const result = method.apply(this, args);
console.log(`Result:`, result);
return result;
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
Validation Decorator
function validate(min: number, max: number) {
return function (target: any, propertyKey: string) {
let value: number;
const getter = () => value;
const setter = (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 Product {
@validate(0, 100)
price: number;
}
Decorator Execution Order
Decorators are applied in a specific order:
- Parameter decorators
- Method/accessor decorators
- Property decorators
- Class decorators
Exercises
Exercise 1: Class Decorator
// Create a decorator 'timing' that:
// - Measures how long a method takes to execute
// - Logs the duration
// - Apply it to a class method
Exercise 2: Method Decorator
// Create a decorator 'deprecated' that:
// - Logs a warning when a deprecated method is called
// - Apply it to an old method
Exercise 3: Property Decorator
// Create a decorator 'readonly' that:
// - Makes a property readonly after initialization
// - Apply it to a class property
Exercise 4: Parameter Decorator
// Create a decorator 'validate' that:
// - Validates a parameter before the function runs
// - Throws an error if validation fails
Exercise 5: Decorator Factory
// Create a decorator factory 'cache' that:
// - Caches method results
// - Takes a TTL (time to live) parameter
// - Returns cached result if available and not expired
Best Practices
- Use decorators sparingly - They can make code harder to understand
- Document decorator behavior - Add comments explaining what they do
- Consider alternatives - Sometimes functions or mixins are clearer
- Test decorators - Ensure they work as expected
- Be aware of execution order - Decorators execute in a specific order
Common Mistakes
- Forgetting to enable decorators
// ❌ Missing in tsconfig.json
{
"compilerOptions": {
// Need experimentalDecorators: true
}
}
- Wrong decorator signature
// ❌ Wrong parameters
function myDecorator(target: any) {
// Method decorator needs 3 parameters
}
// ✅ Correct
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// ...
}
- Not returning descriptor
// ❌ Forgetting to return
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.value = function() { /* ... */ };
// Missing return descriptor;
}
// ✅ Correct
function myDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.value = function() { /* ... */ };
return descriptor;
}
Next Steps
Learn about Type Guards.