Decorators
45 minDecorators are a powerful TypeScript feature that enables meta-programming—code that writes or modifies other code. They provide a declarative way to add functionality to classes, methods, properties, and parameters without modifying the original code. Decorators use the `@` symbol syntax and are widely used in frameworks like Angular, NestJS, and TypeORM for dependency injection, validation, routing, and more.
Decorators are functions that receive information about the target they're decorating and can return a modified version or perform side effects. Class decorators receive the constructor function, method decorators receive the target object, property name, and property descriptor, and parameter decorators receive the target, property name, and parameter index. This information allows decorators to inspect and modify the decorated element.
Decorator factories are functions that return decorator functions, enabling you to pass parameters to decorators. For example, `@log('prefix')` is a decorator factory that creates a decorator with a specific prefix. Decorator factories are essential for creating reusable, configurable decorators that can be customized for different use cases.
The decorator execution order is important: decorators are applied from bottom to top for expressions, but executed from top to bottom. This means if you have `@decorator1` and `@decorator2` on the same element, `decorator2` is applied first, then `decorator1`. Understanding this order is crucial when decorators depend on each other or modify the same target.
While decorators are still an experimental feature in TypeScript (as of ES2022), they're widely used in the TypeScript ecosystem. To use decorators, you need to enable `experimentalDecorators` and optionally `emitDecoratorMetadata` in your `tsconfig.json`. The TypeScript team is working on standardizing decorators, and they may become a standard JavaScript feature in the future.
Common decorator patterns include logging, validation, caching, dependency injection, and aspect-oriented programming. Decorators enable you to separate cross-cutting concerns from business logic, making code more maintainable and following the single responsibility principle. However, they should be used judiciously, as they can make code harder to understand if overused.
Key Concepts
- Decorators use the @ symbol and enable meta-programming.
- Decorators can be applied to classes, methods, properties, and parameters.
- Decorator factories are functions that return decorator functions.
- Decorators are executed in a specific order (bottom to top application, top to bottom execution).
- Decorators require experimentalDecorators to be enabled in tsconfig.json.
Learning Objectives
Master
- Creating and using decorators for classes, methods, and properties
- Understanding decorator factories and parameter passing
- Configuring TypeScript for decorator support
- Implementing common decorator patterns (logging, validation, caching)
Develop
- Meta-programming and aspect-oriented programming concepts
- Understanding decorator execution order and behavior
- Designing reusable decorator patterns
Tips
- Enable experimentalDecorators in tsconfig.json to use decorators.
- Use decorator factories when you need to pass parameters to decorators.
- Understand decorator execution order when using multiple decorators.
- Use decorators for cross-cutting concerns like logging and validation.
Common Pitfalls
- Overusing decorators, making code harder to understand.
- Not understanding decorator execution order, leading to unexpected behavior.
- Forgetting to enable experimentalDecorators in tsconfig.json.
- Creating decorators with side effects that are hard to test.
Summary
- Decorators enable meta-programming with the @ symbol syntax.
- They can be applied to classes, methods, properties, and parameters.
- Decorator factories allow parameterized decorators.
- Decorators require experimentalDecorators to be enabled.
- They're useful for cross-cutting concerns but should be used judiciously.
Exercise
Create a simple decorator that logs method calls.
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned: ${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);
Exercise Tips
- Enable decorators in tsconfig.json: { "experimentalDecorators": true, "emitDecoratorMetadata": true }
- Create parameter decorators: function Required(target: any, propertyKey: string, parameterIndex: number) { ... }
- Use class decorators: function Component(constructor: Function) { ... }
- Create decorator factories: function log(prefix: string) { return function(target, propertyKey, descriptor) { ... }; }