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.
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.
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");
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
experimentalDecorators
tofalse
intsconfig.json
and observe any changes in code behavior. Don’t forget to set it back totrue
for proper decorator functionality.
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.
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