In TypeScript, decorators are a special kind of declaration that can be attached to a class, method, accessor, property, or parameter. They provide a way to modify the behavior of these entities. Decorators can be thought of as syntactic sugar that allows you to express the structure of your code succinctly.
There are several types of decorators in TypeScript:
Class Decorators: A class decorator is applied to the class constructor. It can be used to add extra functionality to the class or modify its metadata.
Method Decorators: A method decorator is applied to the methods of the class. It can be used to alter the method’s functionality or behavior.
Accessor Decorators: Like method decorators, these decorators are applied to getters or setters in a class.
Property Decorators: Property decorators are applied to properties of a class. They can be used to add metadata or modify the property behavior.
Parameter Decorators: A parameter decorator is applied to the parameter of a method. It is typically used to add metadata to the method parameters.
Let’s explore each type with practical examples.
Here’s an example of a simple class decorator that logs the creation of a class instance:
function LogClass(target: Function) { const original = target; function construct(constructor: any, args: any[]) { console.log(`Creating new instance of ${constructor.name} with args: ${args}`); return new constructor(...args); } const newConstructor: any = function (...args: any[]) { return construct(original, args); }; newConstructor.prototype = original.prototype; return newConstructor; } @LogClass class User { constructor(public name: string) { console.log(`${this.name} has been created.`); } } const user = new User("Alice");
In this example, the LogClass
decorator will log each instance of the User
class created.
Method decorators can be used to control method behavior. Here’s how to do it:
function LogMethod(target: any, propertyName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyName} with`, args); return originalMethod.apply(this, args); }; return descriptor; } class Calculator { @LogMethod add(a: number, b: number): number { return a + b; } } const calculator = new Calculator(); calculator.add(5, 3);
In this scenario, the LogMethod
decorator logs the arguments with which the add
method is called.
You can implement accessor decorators for properties:
function LogAccessor(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalGetter = descriptor.get; descriptor.get = function () { console.log(`Getting value of ${propertyKey}`); return originalGetter?.call(this); }; return descriptor; } class Person { private _age: number = 30; @LogAccessor get age() { return this._age; } } const person = new Person(); console.log(person.age);
This example uses the LogAccessor
decorator to log when the age
property is accessed.
You can add metadata to class properties via property decorators:
function Required(target: any, propertyKey: string) { let value: any; const getter = () => value; const setter = (newValue: any) => { if (newValue === null || newValue === undefined) { console.error(`Error: ${propertyKey} is required.`); } else { value = newValue; } }; Object.defineProperty(target, propertyKey, { get: getter, set: setter }); } class Profile { @Required username!: string; } const profile = new Profile(); profile.username = null; // This will log an error
In this case, the Required
decorator validates the value assigned to the property.
Finally, let’s see parameter decorators in action:
function LogParameter(target: any, methodName: string, parameterIndex: number) { const existingParams = Reflect.getOwnMetadata('params', target, methodName) || []; existingParams.push(parameterIndex); Reflect.defineMetadata('params', existingParams, target, methodName); } class Message { send(@LogParameter recipient: string) { console.log(`Message sent to ${recipient}`); } } const msg = new Message(); msg.send('John Doe');
In this scenario, LogParameter
records the index of the parameter, which can be useful for logging or validation.
Decorators in TypeScript are a powerful feature that allows for cleaner, more organized code by encapsulating logging, validation, and other cross-cutting concerns. With their varied application across classes, methods, properties, accessors, and parameters, decorators build upon the foundational principles of object-oriented programming, making your designs more expressive and maintainable. Whether you are validating a property, logging method calls, or altering class instantiation, decorators can make your TypeScript experience far more enjoyable and structured.
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript
17/10/2024 | TypeScript