How to use decorators in TypeScript
Decorators are a powerful feature in TypeScript that allows developers to control the behavior of classes, methods, properties, and parameters. They provide an effective way to organize code, separate concerns, and promote code reuse.
Basics of decorators in TypeScript
To define a decorator, we write a function that takes the target element as its argument and returns a new value or performs some action. The decorator function is then applied using the @ symbol, followed by the decorator name, placed just before the target element declaration.
There are four main types of decorators in TypeScript:
-
Parameter decorators: These are applied to the parameters of a method or constructor, allowing us to manipulate or add metadata to the parameters.
-
Class decorators: These are applied to classes, allowing us to modify their behavior or add metadata.
-
Method decorators: These are applied to methods within a class, enabling us to alter their behavior or add additional functionality.
-
Property decorators: These are applied to class properties, providing a way to modify their behavior or add metadata.
In TypeScript, the evaluation order of decorators is structured to ensure consistency and predictability during the decoration process. The order in which decorators are invoked follows a clear pattern:
For each instance member, Parameter decorators take precedence, succeeded by Method, Accessor, or Property decorators.
For each static member, Parameter decorators have priority, followed by Method, Accessor, or Property decorators.
Parameter decorators are designated for the constructor.
Class decorators are meant for the entire class structure.
In object-oriented programming, instance members refer to the properties and methods of a class's instances (objects). These members are unique to each instance and can have different values or states.
On the other hand, static members belong to the class itself rather than instances. They are shared among all class instances and accessed using the class name. Static members often represent shared properties or behaviors not specific to individual instances.
Creating and using decorators
In this example, we’ll delve into a demonstration of TypeScript decorators and explore all four significant types of decorators.
Let’s dive into the code to see how these decorators can be effectively applied.
// Parameter decoratorfunction logParameter(target: any, propertyKey: string, parameterIndex: number) {console.log(`Parameter decorator called on ${propertyKey} parameter ${parameterIndex}`);}// Class decoratorfunction logClass(target: any) {console.log(`Class decorator called on class: ${target.name}`);}// Method decoratorfunction logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {const originalMethod = descriptor.value;// Modify the method behaviordescriptor.value = function (...args: any[]) {console.log(`Method decorator called on method: ${propertyKey}`);const result = originalMethod.apply(this, args);return result;};return descriptor;}// Property decoratorfunction logProperty(target: any, propertyKey: string) {console.log(`Property decorator called on property: ${propertyKey}`);}@logClassclass Example {@logPropertymessage: string = "Hello, decorator!";@logMethodgreet(@logParameter name: string) {console.log(`Hello, ${name}!`);}}const instance = new Example();instance.greet("Learner");
Explanation
Lines 2–4: We define the logParameter decorator. This decorator is intended to log information about the parameter it’s applied to when the decorated method is invoked.
Lines 6–9: We define the logClass decorator. This decorator logs a message indicating that the decorator has been called, and it displays the name of the class when it is defined.
Lines 12-20: We define the logMethod decorator. Inside this decorator, we store a reference to the original method of the decorated class in the originalMethod variable.
Lines 26–28: We define the logProperty decorator. This decorator logs a message when the decorated property is accessed.
Line 30: We apply the @logClass decorator to the Example class. This decorator logs a message when the class is defined, displaying the class name.
Lines 32–33: Inside the Example class, we define a property named message and apply the @logProperty decorator to it. This logs a message when the property is accessed.
Lines 35–38: We apply the @logMethod decorator to the declared greet method. This decorator modifies the behavior of the method by wrapping it with logging statements. Also, the @logParameter decorator is applied to the name parameter of the greet method. This logs a message when the parameter decorator is called on the decorated method.
Lines 41–42: We call the greet method of the instance object with the argument "Learner". This triggers the decorated method and its associated decorators, resulting in log messages being displayed in the console.
Note: Experiment by toggling
experimentalDecoratorstofalseintsconfig.jsonand observe any changes in code behavior. Don’t forget to set it back totruefor proper decorator functionality.
Key use cases of decorators
Notably, widely used frameworks like Angular and NestJS leverage decorators extensively to streamline various aspects of component definition, dependency injection, and routing, underscoring their significance in modern web development.
Limitations of decorators
Decorators in TypeScript are powerful features, but it’s essential to be aware of their limitations. Decorators are currently an experimental feature in TypeScript, which means they might undergo changes or refinements in future versions of the language. As a result, there could be compatibility issues or variations in behavior between different TypeScript versions.
Furthermore, there are some specific limitations to consider. Decorators can only be applied to classes, methods, properties, or parameters. They cannot be directly applied to function expressions or arrow functions. Additionally, it’s worth noting that decorators cannot be used on function or class declarations.
Free Resources