TypeScript Decorators

This chapter explores TypeScript decorators, a powerful feature for enhancing and modifying classes, methods, properties, or parameters at runtime. Decorators provide a declarative way to implement cross-cutting concerns like logging, validation, or dependency injection.

Chapter Goal

  • To understand what decorators are and how they work in TypeScript.
  • To learn how to create and use different types of decorators.
  • To explore practical applications of decorators in TypeScript projects.

Key Characteristics for TypeScript Decorators

  • Metadata Annotation: Decorators attach metadata to classes or class members.
  • Runtime Execution: Executed at runtime to modify behavior or add functionality.
  • Extensibility: Enable reusable and configurable behavior across code.
  • Support for Angular: Widely used in frameworks like Angular for dependency injection and metadata.
  • Types: Include class, method, accessor, property, and parameter decorators.

Basic Rules for TypeScript Decorators

  1. Use the @ symbol followed by the decorator name.
  2. Ensure experimentalDecorators is enabled in the tsconfig.json file.
  3. Apply decorators to classes, methods, properties, or parameters.
  4. Decorators can be stacked and executed in a specific order.
  5. Combine decorators with metadata reflection for advanced use cases.

Best Practices

  1. Use decorators to encapsulate reusable behaviors.
  2. Document custom decorators for better readability and maintainability.
  3. Avoid overusing decorators to maintain code simplicity.
  4. Combine decorators with dependency injection for cleaner architecture.
  5. Test decorator behavior thoroughly to ensure reliability.

Syntax Table

Serial No Component Syntax Example Description
1 Class Decorator @Component Enhances class functionality or metadata.
2 Method Decorator @Log Modifies method behavior or tracks execution.
3 Property Decorator @Inject Attaches metadata to a property for dependency injection.
4 Accessor Decorator @Validate Applies validation logic to a getter or setter.
5 Parameter Decorator @Param Adds metadata to method parameters.

Syntax Explanation

1. Class Decorator

What is a Class Decorator

A decorator in TypeScript applied to a class to attach metadata, enhance its capabilities, or modify its behavior at runtime. This is often used to add reusable properties or functionalities to the class, making it an essential tool for frameworks like Angular.

Syntax

function Component(target: Function): void {

  target.prototype.componentName = target.name;

}

 

@Component

class AppComponent {}

Detailed Explanation

  • The Component decorator adds a componentName property to the class prototype.
  • Used widely in frameworks like Angular for defining components.

Example

@Component

class AppComponent {}

 

console.log(new AppComponent().componentName); // Output: AppComponent

Output

AppComponent

Notes

  • Use class decorators to apply cross-cutting concerns to entire classes.

Warnings

  • Ensure decorators are used appropriately to avoid unexpected side effects.

2. Method Decorator

What is a Method Decorator

A decorator in TypeScript used to wrap, enhance, or intercept the behavior of a method at runtime. Method decorators allow developers to add additional functionality such as logging, input validation, or performance monitoring while maintaining the core method logic.

Syntax

function Log(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {

  const originalMethod = descriptor.value;

 

  descriptor.value = function (…args: any[]) {

    console.log(`Method ${propertyKey} called with args:`, args);

    return originalMethod.apply(this, args);

  };

}

 

class Calculator {

  @Log

  add(a: number, b: number): number {

    return a + b;

  }

}

Detailed Explanation

  • The Log decorator wraps the add method, logging arguments before execution.
  • Enhances the method without modifying its core logic.

Example

const calculator = new Calculator();

console.log(calculator.add(2, 3)); // Logs: Method add called with args: [2, 3] and Output: 5

Output

Method add called with args: [2, 3]

5

Notes

  • Use method decorators for logging, validation, or performance tracking.

Warnings

  • Avoid modifying the original method’s logic in unexpected ways.

3. Property Decorator

What is a Property Decorator

A decorator in TypeScript used to attach metadata or alter the behavior of a property at runtime. This is often employed in scenarios like dependency injection, logging, or defining configurations for frameworks.

Syntax

function Inject(target: Object, propertyKey: string): void {

  console.log(`Injected into property: ${propertyKey}`);

}

 

class Service {

  @Inject

  apiUrl: string;

}

Detailed Explanation

  • The Inject decorator adds metadata to the apiUrl property.
  • Commonly used in dependency injection frameworks.

Example

const service = new Service();

// Logs: Injected into property: apiUrl

Output

Injected into property: apiUrl

Notes

  • Use property decorators for injecting dependencies or setting metadata.

Warnings

  • Ensure that injected dependencies are resolved correctly to avoid runtime errors.

4. Accessor Decorator

What is an Accessor Decorator

A specialized decorator in TypeScript used on getters or setters to enforce specific rules, validate input values, or modify their runtime behavior. This helps ensure data integrity and allows for centralized control over property access.

Syntax

function Validate(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {

  const originalSetter = descriptor.set;

 

  descriptor.set = function (value: any) {

    if (value < 0) {

      throw new Error(‘Value must be non-negative’);

    }

    originalSetter!.call(this, value);

  };

}

 

class Account {

  private _balance: number = 0;

 

  @Validate

  set balance(amount: number) {

    this._balance = amount;

  }

 

  get balance(): number {

    return this._balance;

  }

}

Detailed Explanation

  • The Validate decorator ensures the balance value is non-negative.
  • Useful for enforcing validation rules on properties.

Example

const account = new Account();

account.balance = 100;

console.log(account.balance); // Output: 100

// account.balance = -50; // Throws Error: Value must be non-negative

Output

100

Notes

  • Use accessor decorators to apply constraints or validations.

Warnings

  • Ensure validation logic is comprehensive to cover all edge cases.

5. Parameter Decorator

What is a Parameter Decorator

A TypeScript feature that allows attaching additional metadata or behavior to a method parameter. This can be used to enable advanced scenarios like runtime validation, logging, or dependency injection, ensuring enhanced control and insight over method execution.

Syntax

function Param(target: Object, propertyKey: string, parameterIndex: number): void {

  console.log(`Parameter index ${parameterIndex} in method ${propertyKey}`);

}

 

class Greeter {

  greet(@Param message: string): void {

    console.log(message);

  }

}

Detailed Explanation

  • The Param decorator logs the index of the decorated parameter.
  • Adds metadata to parameters for advanced use cases like validation or logging.

Example

const greeter = new Greeter();

greeter.greet(‘Hello!’); // Logs: Parameter index 0 in method greet and Output: Hello!

Output

Parameter index 0 in method greet

Hello!

Notes

  • Use parameter decorators for logging or metadata collection.

Warnings

  • Avoid overcomplicating parameter logic, which can reduce readability.

Real-Life Project

Project Name

API Request Logger

Project Goal

Demonstrates how to use decorators for logging API requests in a service class.

Code for This Project

function LogRequest(target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {

  const originalMethod = descriptor.value;




  descriptor.value = async function (...args: any[]) {

    console.log(`Request to API with args:`, args);

    const result = await originalMethod.apply(this, args);

    console.log(`Response from API:`, result);

    return result;

  };

}




class ApiService {

  @LogRequest

  async fetchData(endpoint: string): Promise<any> {

    // Simulate API call

    return { data: `Response from ${endpoint}` };

  }

}




const apiService = new ApiService();

apiService.fetchData('/users');

Save and Run

  1. Save the code in your development environment, such as Visual Studio Code.
  2. Compile the TypeScript code into JavaScript using tsc.
  3. Run the resulting JavaScript file using node.

Expected Output

Request to API with args: [‘/users’]

Response from API: { data: ‘Response from /users’ }

Insights

  • Decorators streamline the implementation of cross-cutting concerns like logging and validation.
  • Combining multiple decorators enables modular and reusable functionality.
  • Real-world examples like API logging highlight their practical benefits.

Key Takeaways

  • Decorators enhance and modify behavior in a declarative way.
  • Use decorators responsibly to maintain code readability and simplicity.
  • Test decorators thoroughly to ensure they work as intended in all scenarios.