Explore the experimental decorator syntax in TypeScript for meta-programming, including class, method, and property decorators with practical examples.
Decorators in TypeScript offer a powerful way to add annotations and meta-programming syntax for class declarations and members. They allow developers to modify classes, methods, properties, and even parameters at design time. Although decorators are an experimental feature, they are widely used in frameworks like Angular for dependency injection and other purposes. In this section, we will explore the concept of decorators, how to enable them, and practical examples of their use.
Decorators are special functions that can be attached to classes, methods, properties, or parameters to modify their behavior. They are a form of meta-programming, which means they allow you to add additional functionality to your code without modifying the actual code itself. Decorators can be used for a variety of purposes, such as logging, validation, or dependency injection.
Before we dive into examples, it’s important to note that decorators are an experimental feature in TypeScript. To use them, you need to enable the experimentalDecorators
option in your tsconfig.json
file. Here’s how you can do it:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
Decorators can be applied to different parts of your code. Let’s explore the main types of decorators available in TypeScript:
Class decorators are functions that are applied to the constructor of a class. They can be used to modify the class or add metadata to it. Let’s look at an example:
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}`;
}
}
const greeter = new Greeter("world");
console.log(greeter.greet());
In this example, the sealed
decorator is applied to the Greeter
class. It seals the constructor and its prototype, preventing any further modifications.
Method decorators are applied to the methods of a class. They can be used to modify the method or add metadata. Here’s an example:
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with arguments: ${args}`);
return originalMethod.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
const calculator = new Calculator();
console.log(calculator.add(2, 3));
In this example, the log
decorator is applied to the add
method of the Calculator
class. It logs the method name and arguments each time the method is called.
Property decorators are applied to properties within a class. They can be used to modify the property or add metadata. Here’s an example:
function readonly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false
});
}
class Person {
@readonly
name: string = "John Doe";
}
const person = new Person();
person.name = "Jane Doe"; // This will throw an error in strict mode
console.log(person.name);
In this example, the readonly
decorator is applied to the name
property of the Person
class, making it immutable.
Parameter decorators are applied to the parameters of a method within a class. They can be used to modify the parameter or add metadata. Here’s an example:
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 User {
greet(@logParameter message: string): string {
return `Hello, ${message}`;
}
}
const user = new User();
console.log(user.greet("world"));
In this example, the logParameter
decorator is applied to the message
parameter of the greet
method in the User
class. It logs the parameter index.
Decorators have a wide range of use cases, including:
It’s important to note that decorators are an experimental feature in TypeScript. This means that the syntax and behavior may change in future versions. Always check the latest TypeScript documentation for updates.
To get a better understanding of decorators, try modifying the examples above. For instance, you can create a decorator that logs the execution time of a method or a decorator that validates the type of a property.
To help visualize how decorators work, let’s look at a simple flowchart:
graph TD; A[Start] --> B[Apply Decorator] B --> C{Is it a Class Decorator?} C -->|Yes| D[Modify Constructor] C -->|No| E{Is it a Method Decorator?} E -->|Yes| F[Modify Method] E -->|No| G{Is it a Property Decorator?} G -->|Yes| H[Modify Property] G -->|No| I{Is it a Parameter Decorator?} I -->|Yes| J[Modify Parameter] J --> K[End] D --> K F --> K H --> K
This flowchart shows the decision-making process when applying decorators to different parts of a class.
For more information on decorators, check out the following resources: