Sling Academy
Home/TypeScript/Decorator Factories in TypeScript: A Complete Guide

Decorator Factories in TypeScript: A Complete Guide

Last updated: January 07, 2024

Introduction

Unpack the power of TypeScript decorators and streamline your coding process by mastering decorator factories, an advanced feature that enhances meta-programming capabilities.

What are Decorators?

Decorators are a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. In TypeScript, decorators provide a way to add annotations and a meta-programming syntax for class declarations and members. Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.

Here is a simple decorator example:

function SimpleDecorator(constructor: Function) {
  console.log('SimpleDecorator called');
}

@SimpleDecorator
class MyClass {
  constructor() {
    console.log('MyClass created');
  }
}

What are Decorator Factories?

Decorator factories in TypeScript allow for customization of decorator functions. They are simply functions that return the expression that will be called by the decorator at runtime.

A basic decorator factory example:

function Color(value: string) {
  return function(target) {
    // This is the decorator
    Object.defineProperty(target, 'color', {
      value,
      writable: false
    });
  };
}

@Color('blue')
class ColoredClass {}

console.log((new ColoredClass() as any).color); // Outputs: 'blue'

Using Decorator Factories

Decorator factories can be used to customize how decorators are applied to classes, methods, accessors, properties, or parameters. By using decorator factories, you can pass parameters to your decorators and return different decorator functions depending on those parameters.

Class Decorators

function Component(selector: string) {
  return function (constructor: Function) {
    constructor.prototype.selector = selector;
  };
}

@Component('app-component')
class AppComponent {}

Method Decorators

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

  propertyDesciptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyName} with`, args);
    return method.apply(this, args);
  };

  return propertyDesciptor;
}

Accessory Decorators

function Format(formatString: string) {
  return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    let originalValue = descriptor.get;

    descriptor.get = function () {
      let result = originalValue.apply(this);
      return `Formatted date: ${result.toLocaleDateString()}`;
    };
  };
}

Property Decorators

function Default(defaultValue: any) {
  return function (target: any, propertyName: string) {
    let value = defaultValue;

    Object.defineProperty(target, propertyName, {
      get: () => value,
      set: (newValue) => { value = newValue; },
      enumerable: true,
      configurable: true
    });
  };
}

Parameter Decorators

function Print(target: Object, methodName: string, parameterIndex: number) {
  console.log(`Print decorator for ${methodName} called on parameter index ${parameterIndex}`);
}

Decorators with Dependencies

Sometimes decorators depend on other decorators, you can compose them to enhance their behavior. Here’s an example:

function First() {
  console.log('First Decorator Factory');
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('First Decorator called');
  };
}

function Second() {
  console.log('Second Decorator Factory');
  return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('Second Decorator called');
  };
}

class ExampleClass {
  @First()
  @Second()
  method() {}
}

In the above example, First will be executed before Second, even though it appears after Second in the code.

Conclusion

Decorator factories in TypeScript give tremendous power to add custom behavior to classes, methods, and more. They bolster the development process, allowing for a declarative and reusable code which is easier to read and maintain. As you continue to practice using decorators and decorator factories, you’ll discover new ways to optimize and enhance your TypeScript applications.

Next Article: TypeScript: How to Create a New Type from an Old Type

Previous Article: Wrapping Methods with Decorators in TypeScript

Series: The First Steps to TypeScript

TypeScript

You May Also Like

  • TypeScript: setInterval() and clearInterval() methods (3 examples)
  • TypeScript sessionStorage: CRUD example
  • Using setTimeout() method with TypeScript (practical examples)
  • Working with window.navigator object in TypeScript
  • TypeScript: Scrolling to a specific location
  • How to resize the current window in TypeScript
  • TypeScript: Checking if an element is a descendant of another element
  • TypeScript: Get the first/last child node of an element
  • TypeScript window.getComputerStyle() method (with examples)
  • Using element.classList.toggle() method in TypeScript (with examples)
  • TypeScript element.classList.remove() method (with examples)
  • TypeScript: Adding Multiple Classes to An Element
  • element.insertAdjacentHTML() method in TypeScript
  • TypeScript – element.innerHTML and element.textContent
  • Using element.removeAttribute() method in TypeScript
  • Working with Document.createElement() in TypeScript
  • Using getElementById() method in TypeScript
  • Using Window prompt() method with TypeScript
  • TypeScript – window.performance.measure() method (with examples)