Year End Sale: Get Upto 40% OFF on Live Training! Offer Ending in
D
H
M
S
Get Now
Decorators in TypeScript

Decorators in TypeScript

15 Oct 2024
Intermediate
477K Views
15 min read
Learn with an interactive course and practical hands-on labs

TypeScript Programming Course

Understanding Decorators in TypeScript

Decorators in TypeScript Before learning this topic let's see what is TypeScript first, TypeScript, a superset of JavaScript, introduces several powerful features that enhance the language's capabilities. Among these, decorators stand out as a powerful tool for adding annotations and modifying behavior in a declarative manner. Inspired by languages like Python and Java, decorators provide a way to meta-program, allowing developers to write cleaner and more maintainable code.

So in this TypeScript Tutorial, We will Understand Decorators in TypeScript including types of decorators in Typescript.

What Are Decorators?

Decorators are special declarations that can be attached to classes, methods, accessors, properties, or parameters. They provide a way to add metadata, modify behavior, or enhance the functionality of the target they are applied to. Essentially, decorators are functions that receive specific arguments based on their target and can perform actions such as:

  • Modifying the class or method definition.
  • Adding new properties or methods.
  • Wrapping or replacing existing methods.
  • Adding metadata for dependency injection or validation.

Decorators help in adhering to the DRY (Don't Repeat Yourself) principle by abstracting repetitive patterns and enabling more declarative code structures.

Enabling Decorators in TypeScript

Before using decorators in TypeScript, you need to enable them in your project's configuration. Decorators are still an experimental feature and require explicit activation.

1. Update tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true, // Optional: Useful for frameworks like Angular
    // ... other options
  }
}

2. Install Necessary Dependencies:

Some decorator functionalities, especially those relying on metadata, might require additional libraries like reflect-metadata.

npm install reflect-metadata --save

Then, import it at the entry point of your application:

import "reflect-metadata";

Types of Decorators

TypeScript supports several types of decorators, each targeting different parts of a class or its members.

1. Class Decorators

Definition: A decorator applied to a class declaration. It can observe, modify, or replace the class definition.

Signature:

function classDecorator<TFunction extends Function>(target: TFunction): TFunction | void

Example:

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return `Hello, ${this.greeting}`;
  }
}

2. Method Decorators

Definition: A decorator applied to a method declaration. It can modify the method's descriptor, enabling behavior like logging, validation, or access control.

Signature:

function methodDecorator(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void

Example:

function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return `Hello, ${this.greeting}`;
  }
}

3. Accessor Decorators

Definition: A decorator applied to a getter or setter. It allows modification of the property descriptor.

Signature:

function accessorDecorator(
  target: Object,
  propertyKey: string | symbol,
  descriptor: PropertyDescriptor
): PropertyDescriptor | void

Example:

function configurable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.configurable = value;
  };
}

class Person {
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }

  @configurable(false)
  get name() {
    return this._name;
  }
}

4. Property Decorators

Definition: A decorator applied to a property declaration. Unlike method decorators, property decorators cannot directly modify property descriptors but can be used to observe or add metadata.

Signature:

function propertyDecorator(target: Object, propertyKey: string | symbol): void

Example:

function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    // Metadata can be attached here using Reflect.metadata
    Reflect.defineMetadata("format", formatString, target, propertyKey);
  };
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }
}

5. Parameter Decorators

Definition: A decorator applied to a method parameter. It can be used to inject dependencies or perform validations.

Signature:

function parameterDecorator(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
): void

Example:

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  // Store metadata about required parameters
  const existingRequiredParameters: number[] =
    Reflect.getOwnMetadata("requiredParameters", target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata("requiredParameters", existingRequiredParameters, target, propertyKey);
}

class UserService {
  greet(@required name: string) {
    return `Hello, ${name}`;
  }
}

Practical Examples

To better understand decorators, let's explore some practical use cases.

1. Logging with Method Decorators

Suppose you want to log every time a method is called along with its arguments. A method decorator can wrap the original method to achieve this.

function Log(
  target: any,
  propertyName: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyName} called with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyName} returned:`, result);
    return result;
  };

  return descriptor;
}

class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }

  @Log
  multiply(a: number, b: number): number {
    return a * b;
  }
}

const calc = new Calculator();
calc.add(2, 3);
// Console Output:
// Method add called with args: [2, 3]
// Method add returned: 5

calc.multiply(4, 5);
// Console Output:
// Method multiply called with args: [4, 5]
// Method multiply returned: 20

2. Singleton with Class Decorators

Ensure a class has only one instance by using a class decorator.

function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    private static instance: any;

    constructor(...args: any[]) {
      if ((constructor).instance) {
        return (constructor).instance;
      }
      super(...args);
      (constructor).instance = this;
    }
  };
}

@Singleton
class Database {
  connection: string;

  constructor() {
    this.connection = "Database connection established";
  }
}

const db1 = new Database();
const db2 = new Database();

console.log(db1 === db2); // true

Use Cases

Decorators are versatile and can be applied in various scenarios, including but not limited to:

  • Dependency Injection: Frameworks like Angular use decorators to inject dependencies into classes.
  • Validation: Automatically validate method parameters or properties based on decorators.
  • Logging and Monitoring: As shown in the logging example, decorators can monitor method calls.
  • Authorization: Restrict access to methods based on user roles or permissions.
  • Metadata Management: Store and retrieve metadata for classes and their members, useful in ORM libraries or serialization.
Conclusion

Decorators in TypeScript provide a declarative way to enhance and modify classes and their members. By abstracting repetitive patterns and enabling meta-programming, decorators contribute to cleaner and more maintainable codebases. While still an experimental feature, their integration into TypeScript opens doors to more expressive and powerful coding paradigms, especially when building large-scale applications or frameworks. As you explore decorators, remember to keep abreast of their evolution in the TypeScript and JavaScript ecosystems, as the standards around them continue to mature. With thoughtful application, decorators can significantly streamline your development workflow and code architecture.

FAQs

Decorators are just a wrapper around a function. They are used to enhance the functionality of the function without modifying the underlying function

At their core, Decorators essentially are mixing in functionality into a prototype, much like Mixins . The difference is that this mixing is obvious through a language construct at the beginning of the class.

A decorator is much like a personal stylist. They're hired to create an atmosphere within the home that aligns with their client's personal style. 

Take our Typescript skill challenge to evaluate yourself!

In less than 5 minutes, with our skill challenge, you can identify your knowledge gaps and strengths in a given skill.

GET FREE CHALLENGE

Share Article
About Author
Amit Kumar (Software Engineer And Author)

Experienced Software Engineer with a demonstrated history of working in the professional training & coaching industry. Skilled in Asp.net MVC, Angular, Language Integrated Query (LINQ), SQL, C++, and HTML. Strong engineering professional graduated from Sathyabama University.
Accept cookies & close this