What are decorators in TypeScript?
Key takeaways
Decorators are a feature in TypeScript that allows developers to modify the behavior of classes, methods, properties, or parameters.
Decorators are defined using the
@symbol followed by the decorator name.The benefits of using decorators are following:
Code reusability: Encapsulate functionalities like logging and validation.
Declarative syntax: Enhance readability and maintainability.
Separation of concerns: Clearly distinguish business logic from additional functionalities.
Limitations to using decorators may be browser support, excessive complexity, or performance overhead.
Decorators are a powerful feature in TypeScript that enables developers to modify the behavior of classes, methods, properties, or parameters in a declarative way. They provide a way to add metadata to code, making it possible to enhance or change the functionality of existing code without modifying it directly. Decorators are a form of metaprogramming that allows us to write reusable code such as logging, validation, dependency injection, etc.
For instance, consider a web application that needs to log user activities and enforce access control. Using decorators, we can automatically log every method call in a class and check user roles before executing sensitive methods.
Syntax for decorators
Decorators are attached to entities (classes, functions, and more) with the following syntax:
@nameOfOurDecorator
The @ symbol is part of the syntax and denotes that the following identifier is a decorator. As the name suggests, after that, we specify the name of our decorator, through which we customize the entity's functionality.
Benefits of decorators
Code reusability: Decorators encapsulate common functionalities like logging, validation, and authentication, promoting code reuse across different application parts and reducing duplication.
Declarative syntax: Decorators clean and declarative syntax makes applying metadata and modifying behavior easier, enhancing code readability and maintainability.
Separation of concerns: By separating concerns into individual decorators, developers can clearly distinguish between core business logic, making it easier to manage.
Limitations of decorators
Browser support: Decorators may not be fully supported in all JavaScript environments, potentially causing compatibility issues, especially in older browsers.
Complexity: Decorators, particularly when nested or used extensively, can introduce complexity to the codebase, making it harder to understand and maintain.
Performance overhead: Applying decorators at runtime can incur a performance overhead, especially in scenarios with complex decorator logic, potentially impacting the application's performance.
Coding example for decorators
If we're running our project locally, we need to edit our tsconfig.json file to something like the following:
{"compilerOptions": {..."experimentalDecorators": true, // This line to enable decorators...}}
Let's create a calculator. In the given code, decorators are used to log each method call in the Calculator class. They automatically record the method name and parameters and return values, providing a clear audit trail for operations like addition, subtraction, multiplication, and division.
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const prev = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`The function called is ${propertyKey} with parameters: ${JSON.stringify(args)}`);
const res = prev.apply(this, args);
console.log(`The function ${propertyKey} returned: ${res}`);
return res;
};
return descriptor;
}
class Calculator {
@logMethod
add(num1: number, num2: number) {
return num1 + num2;
}
@logMethod
sub(num1: number, num2: number) {
return num1 - num2;
}
@logMethod
mul(num1: number, num2: number) {
return num1 * num2;
}
@logMethod
div(a: number, b: number) {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
}
const calculator = new Calculator();
console.log(calculator.add(23, 8));
console.log(calculator.sub(6, 2));
console.log(calculator.mul(9, 7));
console.log(calculator.div(30, 10));
Code explanation
The code above can be explained as follows:
Line 1: Defines the
logMethoddecorator function.Line 4: Replaces the original method with a modified version that adds logging.
Lines 5–7: Logs the method name, parameters, and return value when the method is called.
Lines 16–18: The
addmethod in theCalculatorclass with the decorator applied.Lines 21–23: The
submethod in theCalculatorclass with the decorator applied.Lines 26–28: The
mulmethod in theCalculatorclass with the decorator applied.Lines 31–36: The
divmethod in theCalculatorclass with the decorator applied, including error handling for division by zero.Lines 39–43: Calls the methods in the
Calculatorclass and logs the results.
Note: In the above code example, each method in the
Calculatorclass is appended with the@logMethoddecorator. This logs the method call with the arguments passed before executing and logging the return value after execution.
Quiz
Lets assess your understanding of decorators by choosing the correct answer in the quiz below:
In the provided example, what is the role of the logMethod function?
To perform arithmetic calculations.
To validate user inputs.
To log method calls and return values.
To handle exceptions during method execution.
Conclusion
Decorators in TypeScript provide a clean and reusable way to enhance code functionality without directly altering it. By attaching them to methods, we can automatically log method calls, validate inputs, or perform other tasks.
Free Resources