Skip to main content
illustration of random abstract shapes

Learning the Basics of JavaScript/TypeScript Decorators

Hello there, fellow coders! Today we’re delving into an exciting aspect of TypeScript: decorators. This feature is not yet part of the JavaScript language standard (as of my knowledge cutoff in 2021) but is available in TypeScript and is widely used in various libraries and frameworks, like Angular.

What Are Decorators?

Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. A decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter.

The syntax for a decorator is an @ symbol, followed by an expression:

@expression

The expression must evaluate to a function that will be called at runtime with information about the decorated declaration. The function could return a value that describes the modifications to be made to the decorated element.

Types of Decorators

Class Decorators

Class decorators are declared just before a class declaration. The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

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

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

Method Decorators

Method decorators are declared just before a method declaration. The decorator is applied to the property descriptor for the method, and can be used to observe, modify, or replace a method definition.

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

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

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

Accessor Decorators

Accessor decorators can be used with either get or set accessor declarations, to observe, modify, or replace an accessor’s definition.

class Point {
  private _x: number;
  private _y: number;

  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }

  @configurable(false)
  get x() {
    return this._x;
  }

  @configurable(false)
  get y() {
    return this._y;
  }
}

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

Property Decorators

Property decorators are declared just before a property declaration. The decorator function is called at runtime with two arguments: the constructor function of the class for a static member or the prototype of the class for an instance member, and the member name.

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

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

  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

let formatMetadataKey = Symbol("format");

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

Parameter Decorators

Parameter decorators are declared just before a parameter declaration. The parameter decorator is applied to the function for a class constructor or method declaration.

class Greeter {
  greeting: string;

  constructor(message: string, @required name: string) {
    this.greeting = message;
  }
}

function required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number,
) {
  let existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propertyKey,
  );
}

In summary, decorators can make your TypeScript code more elegant, readable, and efficient by allowing you to modify or encapsulate behaviors at a higher level of abstraction. Decorators can sometimes feel like magic, but once you start using them, they become a potent tool in your TypeScript toolbox.

As always, keep exploring and enjoy your journey into the magical world of TypeScript! Happy coding!

Stay in touch

Don't miss out on new posts or project updates. Hit me up on X (Twitter) for updates, queries, or some good ol' tech talk.

Follow @zkMake
Zubin Khavarian's Profile Written by Zubin Khavarian