41. Decorators in TypeScript
Page 71 | Listen in audio
Decorators in TypeScript are a powerful feature that allows developers to modify the behavior of classes, methods, properties, and parameters. They are a form of metadata annotation that can be applied to code, providing a way to add additional information or functionality in a declarative manner. Decorators are inspired by a similar feature in languages like Python and Java, and they are widely used in frameworks such as Angular to enhance code readability and maintainability.
At their core, decorators are simply functions that are prefixed with an @
symbol and are executed at runtime. They can be used to observe, modify, or replace the definition of the target they are applied to. Decorators are applied to classes and their members, and they can be categorized into several types: class decorators, method decorators, accessor decorators, property decorators, and parameter decorators.
Class Decorators
Class decorators are applied to the constructor of a class. They can be used to modify or replace the class definition. A class decorator is a function that takes a single parameter, which is the constructor of the class being decorated.
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return `Hello, ${this.greeting}`;
}
}
In this example, the @sealed
decorator is applied to the Greeter
class. The sealed
function seals the constructor and its prototype, preventing new properties from being added to the class.
Method Decorators
Method decorators are applied to methods within a class. They can be used to modify the method's behavior. A method decorator is a function that takes three arguments: the target object, the name of the method, and the property descriptor of the method.
function enumerable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Person {
constructor(public name: string) {}
@enumerable(false)
greet() {
return `Hello, ${this.name}`;
}
}
In this example, the @enumerable(false)
decorator is applied to the greet
method of the Person
class. The decorator modifies the enumerable
property of the method's descriptor, making it non-enumerable.
Accessor Decorators
Accessor decorators are applied to getters and setters within a class. They are similar to method decorators and can be used to modify the behavior of the accessor. An accessor decorator takes the same three arguments as a method decorator.
function configurable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Car {
private _speed: number = 0;
@configurable(false)
get speed() {
return this._speed;
}
set speed(value: number) {
this._speed = value;
}
}
Here, the @configurable(false)
decorator is applied to the speed
getter of the Car
class, making the property non-configurable.
Property Decorators
Property decorators are applied to properties within a class. They are used to observe or modify the property definition. A property decorator is a function that takes two arguments: the target object and the name of the property.
function logProperty(target: any, key: string) {
let value = target[key];
const getter = () => {
console.log(`Get: ${key} => ${value}`);
return value;
};
const setter = (newVal) => {
console.log(`Set: ${key} => ${newVal}`);
value = newVal;
};
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class Product {
@logProperty
price: number;
}
In this example, the @logProperty
decorator is applied to the price
property of the Product
class. The decorator logs access to the property, providing insight into when the property is read or modified.
Parameter Decorators
Parameter decorators are applied to the parameters of a method. They can be used to modify the behavior of the parameter. A parameter decorator is a function that takes three arguments: the target object, the name of the method, and the index of the parameter in the method's parameter list.
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
const metadataKey = `log_${propertyKey}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(parameterIndex);
} else {
target[metadataKey] = [parameterIndex];
}
}
class Calculator {
add(@logParameter a: number, b: number): number {
return a + b;
}
}
In this example, the @logParameter
decorator is applied to the first parameter of the add
method in the Calculator
class. The decorator logs the index of the parameter, allowing for additional behavior to be implemented based on the parameter's usage.
Decorator Factories
Decorator factories are functions that return a decorator function. They allow for the creation of configurable decorators, providing more flexibility in their implementation. Decorator factories are commonly used to pass arguments to decorators.
function log(message: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`${message}: ${args}`);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class Logger {
@log('Executing method')
execute(task: string) {
console.log(`Task: ${task}`);
}
}
In this example, the @log('Executing method')
decorator factory is used to create a decorator that logs a custom message before executing the execute
method in the Logger
class.
Use Cases and Best Practices
Decorators are widely used in modern TypeScript applications, especially in frameworks like Angular. They provide a clean and expressive way to add functionality to classes and their members. Some common use cases for decorators include:
- Logging: Decorators can be used to log method calls, parameter values, and property access.
- Validation: Decorators can enforce validation rules on method parameters or class properties.
- Authorization: Decorators can restrict access to certain methods based on user roles or permissions.
- Caching: Decorators can cache method results to improve performance.
When using decorators, it's important to follow best practices to maintain code quality and readability:
- Keep decorators simple: Avoid adding too much logic to decorators. They should be focused on a single responsibility.
- Use decorator factories: When a decorator requires configuration, use a factory to pass arguments.
- Document decorators: Provide clear documentation for decorators to explain their purpose and usage.
- Test decorators: Ensure that decorators are thoroughly tested to avoid unexpected behavior in the application.
In conclusion, decorators are a versatile feature in TypeScript that enable developers to enhance the functionality of their code in a declarative and reusable manner. By understanding the different types of decorators and their use cases, developers can leverage this feature to build more robust and maintainable applications.
Now answer the exercise about the content:
What is the primary purpose of decorators in TypeScript?
You are right! Congratulations, now go to the next page
You missed! Try again.
Next page of the Free Ebook: