This chapter explores TypeScript decorators, a powerful feature for enhancing and modifying classes, methods, properties, or parameters at runtime. Decorators provide a declarative way to implement cross-cutting concerns like logging, validation, or dependency injection.
Chapter Goal
- To understand what decorators are and how they work in TypeScript.
- To learn how to create and use different types of decorators.
- To explore practical applications of decorators in TypeScript projects.
Key Characteristics for TypeScript Decorators
- Metadata Annotation: Decorators attach metadata to classes or class members.
- Runtime Execution: Executed at runtime to modify behavior or add functionality.
- Extensibility: Enable reusable and configurable behavior across code.
- Support for Angular: Widely used in frameworks like Angular for dependency injection and metadata.
- Types: Include class, method, accessor, property, and parameter decorators.
Basic Rules for TypeScript Decorators
- Use the @ symbol followed by the decorator name.
- Ensure experimentalDecorators is enabled in the tsconfig.json file.
- Apply decorators to classes, methods, properties, or parameters.
- Decorators can be stacked and executed in a specific order.
- Combine decorators with metadata reflection for advanced use cases.
Best Practices
- Use decorators to encapsulate reusable behaviors.
- Document custom decorators for better readability and maintainability.
- Avoid overusing decorators to maintain code simplicity.
- Combine decorators with dependency injection for cleaner architecture.
- Test decorator behavior thoroughly to ensure reliability.
Syntax Table
Serial No | Component | Syntax Example | Description |
1 | Class Decorator | @Component | Enhances class functionality or metadata. |
2 | Method Decorator | @Log | Modifies method behavior or tracks execution. |
3 | Property Decorator | @Inject | Attaches metadata to a property for dependency injection. |
4 | Accessor Decorator | @Validate | Applies validation logic to a getter or setter. |
5 | Parameter Decorator | @Param | Adds metadata to method parameters. |
Syntax Explanation
1. Class Decorator
What is a Class Decorator
A decorator in TypeScript applied to a class to attach metadata, enhance its capabilities, or modify its behavior at runtime. This is often used to add reusable properties or functionalities to the class, making it an essential tool for frameworks like Angular.
Syntax
function Component(target: Function): void {
target.prototype.componentName = target.name;
}
@Component
class AppComponent {}
Detailed Explanation
- The Component decorator adds a componentName property to the class prototype.
- Used widely in frameworks like Angular for defining components.
Example
@Component
class AppComponent {}
console.log(new AppComponent().componentName); // Output: AppComponent
Output
AppComponent
Notes
- Use class decorators to apply cross-cutting concerns to entire classes.
Warnings
- Ensure decorators are used appropriately to avoid unexpected side effects.
2. Method Decorator
What is a Method Decorator
A decorator in TypeScript used to wrap, enhance, or intercept the behavior of a method at runtime. Method decorators allow developers to add additional functionality such as logging, input validation, or performance monitoring while maintaining the core method logic.
Syntax
function Log(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
const originalMethod = descriptor.value;
descriptor.value = function (…args: any[]) {
console.log(`Method ${propertyKey} called with args:`, args);
return originalMethod.apply(this, args);
};
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b;
}
}
Detailed Explanation
- The Log decorator wraps the add method, logging arguments before execution.
- Enhances the method without modifying its core logic.
Example
const calculator = new Calculator();
console.log(calculator.add(2, 3)); // Logs: Method add called with args: [2, 3] and Output: 5
Output
Method add called with args: [2, 3]
5
Notes
- Use method decorators for logging, validation, or performance tracking.
Warnings
- Avoid modifying the original method’s logic in unexpected ways.
3. Property Decorator
What is a Property Decorator
A decorator in TypeScript used to attach metadata or alter the behavior of a property at runtime. This is often employed in scenarios like dependency injection, logging, or defining configurations for frameworks.
Syntax
function Inject(target: Object, propertyKey: string): void {
console.log(`Injected into property: ${propertyKey}`);
}
class Service {
@Inject
apiUrl: string;
}
Detailed Explanation
- The Inject decorator adds metadata to the apiUrl property.
- Commonly used in dependency injection frameworks.
Example
const service = new Service();
// Logs: Injected into property: apiUrl
Output
Injected into property: apiUrl
Notes
- Use property decorators for injecting dependencies or setting metadata.
Warnings
- Ensure that injected dependencies are resolved correctly to avoid runtime errors.
4. Accessor Decorator
What is an Accessor Decorator
A specialized decorator in TypeScript used on getters or setters to enforce specific rules, validate input values, or modify their runtime behavior. This helps ensure data integrity and allows for centralized control over property access.
Syntax
function Validate(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
const originalSetter = descriptor.set;
descriptor.set = function (value: any) {
if (value < 0) {
throw new Error(‘Value must be non-negative’);
}
originalSetter!.call(this, value);
};
}
class Account {
private _balance: number = 0;
@Validate
set balance(amount: number) {
this._balance = amount;
}
get balance(): number {
return this._balance;
}
}
Detailed Explanation
- The Validate decorator ensures the balance value is non-negative.
- Useful for enforcing validation rules on properties.
Example
const account = new Account();
account.balance = 100;
console.log(account.balance); // Output: 100
// account.balance = -50; // Throws Error: Value must be non-negative
Output
100
Notes
- Use accessor decorators to apply constraints or validations.
Warnings
- Ensure validation logic is comprehensive to cover all edge cases.
5. Parameter Decorator
What is a Parameter Decorator
A TypeScript feature that allows attaching additional metadata or behavior to a method parameter. This can be used to enable advanced scenarios like runtime validation, logging, or dependency injection, ensuring enhanced control and insight over method execution.
Syntax
function Param(target: Object, propertyKey: string, parameterIndex: number): void {
console.log(`Parameter index ${parameterIndex} in method ${propertyKey}`);
}
class Greeter {
greet(@Param message: string): void {
console.log(message);
}
}
Detailed Explanation
- The Param decorator logs the index of the decorated parameter.
- Adds metadata to parameters for advanced use cases like validation or logging.
Example
const greeter = new Greeter();
greeter.greet(‘Hello!’); // Logs: Parameter index 0 in method greet and Output: Hello!
Output
Parameter index 0 in method greet
Hello!
Notes
- Use parameter decorators for logging or metadata collection.
Warnings
- Avoid overcomplicating parameter logic, which can reduce readability.
Real-Life Project
Project Name
API Request Logger
Project Goal
Demonstrates how to use decorators for logging API requests in a service class.
Code for This Project
function LogRequest(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
console.log(`Request to API with args:`, args);
const result = await originalMethod.apply(this, args);
console.log(`Response from API:`, result);
return result;
};
}
class ApiService {
@LogRequest
async fetchData(endpoint: string): Promise<any> {
// Simulate API call
return { data: `Response from ${endpoint}` };
}
}
const apiService = new ApiService();
apiService.fetchData('/users');
Save and Run
- Save the code in your development environment, such as Visual Studio Code.
- Compile the TypeScript code into JavaScript using tsc.
- Run the resulting JavaScript file using node.
Expected Output
Request to API with args: [‘/users’]
Response from API: { data: ‘Response from /users’ }
Insights
- Decorators streamline the implementation of cross-cutting concerns like logging and validation.
- Combining multiple decorators enables modular and reusable functionality.
- Real-world examples like API logging highlight their practical benefits.
Key Takeaways
- Decorators enhance and modify behavior in a declarative way.
- Use decorators responsibly to maintain code readability and simplicity.
- Test decorators thoroughly to ensure they work as intended in all scenarios.