TypeScript decorators are a powerful feature that can transform your code and enhance your application’s functionality. Whether you’re building complex web applications or maintaining large codebases, understanding decorators is crucial for modern TypeScript development.
In this comprehensive guide, we’ll explore everything you need to know about TypeScript decorators, from basic concepts to practical applications. We’ll build upon the foundation established in our TypeScript Functions guide while introducing new concepts and advanced techniques.
Table of Contents
- What Are TypeScript Decorators?
- Enabling Decorators in TypeScript
- Types of Decorators
- Practical Applications
- Best Practices
- Common Pitfalls to Avoid
- Integration with Frameworks
- When to Use Decorators
- Future of Decorators
- Conclusion
What Are TypeScript Decorators?
Decorators are special kinds of declarations that can be attached to class declarations, methods, properties, or parameters. They use the form @expression
, where expression
must evaluate to a function that will be called at runtime with information about the decorated declaration.
Enabling Decorators in TypeScript
Before we can use decorators, we need to enable them in our TypeScript configuration. Add the following to your tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Code language: JSON / JSON with Comments (json)
Types of Decorators
Class Decorators
Class decorators are applied to the class constructor and can be used to observe, modify, or replace a class definition.
function Logger(target: Function) {
console.log(`Creating instance of: ${target.name}`);
}
@Logger
class Person {
constructor(public name: string) {}
}
const person = new Person('John'); // Logs: Creating instance of: Person
Code language: JavaScript (javascript)
Method Decorators
Method decorators can be used to observe, modify, or replace a method definition.
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
return original.apply(this, args);
};
}
class Calculator {
@Log
add(a: number, b: number) {
return a + b;
}
}
Code language: JavaScript (javascript)
Property Decorators
Property decorators allow you to observe or modify property definitions.
function Required(target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newVal: string) => {
if (!newVal) {
throw new Error(`${propertyKey} is required`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
}
class User {
@Required
name: string;
}
Code language: JavaScript (javascript)
Practical Applications
Authentication Decorator
Here’s a practical example of using decorators for authentication:
function RequireAuth(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
if (!isAuthenticated()) {
throw new Error('User not authenticated');
}
return original.apply(this, args);
};
}
class UserService {
@RequireAuth
getUserData() {
return { name: 'John', email: '[email protected]' };
}
}
Code language: JavaScript (javascript)
Performance Monitoring
Create a decorator to measure method execution time:
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} execution time: ${end - start}ms`);
return result;
};
}
class ApiService {
@Measure
async fetchData() {
// Simulated API call
await new Promise(resolve => setTimeout(resolve, 1000));
return { data: 'some data' };
}
}
Code language: JavaScript (javascript)
Best Practices
- Keep Decorators Focused: Each decorator should have a single responsibility.
- Document Decorator Behavior: Clearly document what each decorator does and its requirements.
- Handle Errors Gracefully: Include proper error handling in your decorators.
- Consider Performance: Be mindful of performance implications when using multiple decorators.
Common Pitfalls to Avoid
- Decorator Order: Multiple decorators are applied in reverse order (bottom-up).
- Mutating Parameters: Avoid mutating decorator parameters directly.
- Heavy Computations: Don’t perform heavy computations in decorator definitions.
Integration with Frameworks
Many modern frameworks leverage decorators extensively. For example, Angular uses decorators like @Component
, @Injectable
, and @Input
. Understanding TypeScript decorators will help you work more effectively with these frameworks.
When to Use Decorators
Decorators are particularly useful for:
- Cross-cutting concerns (logging, authentication)
- Validation
- Method overriding
- Dependency injection
- Performance monitoring
Future of Decorators
The decorator proposal is still evolving. Stay updated with the latest TypeScript releases for new decorator features and improvements. You can follow our TypeScript Overview for the latest updates.
Conclusion
TypeScript decorators are a powerful feature that can help you write more maintainable and reusable code. They provide a clean way to add functionality to your classes and methods without cluttering your business logic.
Start small with simple decorators and gradually explore more complex use cases as you become comfortable with the concept. Remember to consider the performance implications and maintainability of your decorators as your application grows.
Happy coding!