Standard Decorators (TS 5)

The TC39 decorators shipped in TypeScript 5 β€” annotations that wrap classes and members.

TypeScript 5.0 adopted the standard (Stage 3) decorators β€” no more experimentalDecorators flag for this modern form. A decorator is a function prefixed with @ that runs when the class is defined, letting you observe or replace what it decorates.

The modern signature

Each decorator receives the target and a context object describing what is being decorated (its kind, name, and hooks like addInitializer):

function logged(
  target: Function,
  context: ClassMethodDecoratorContext
) {
  return function (this: any, ...args: any[]) {
    console.log(`calling ${String(context.name)}`);
    return target.apply(this, args);
  };
}

class Api {
  @logged
  fetch(url: string) { return `GET ${url}`; }
}

Method decorators return a replacement function; class decorators can return a new class. The old experimentalDecorators parameter decorators and reflect-metadata style are a separate, legacy system β€” for new code, prefer the standard form.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • A class decorator logs when any instance of the decorated class is created in a dependency injection container.
  • A method decorator wraps async methods to automatically retry on failure without changing the method's business logic.
  • An accessor decorator validates that a setter value is within a valid range before assigning it to the property.

More examples

Class decorator (TS 5)

A class decorator wraps the constructor to log whenever a new instance is created, using the TS 5 decorator context.

Example Β· ts
function Logged<T extends { new (...args: any[]): object }>(Base: T, ctx: ClassDecoratorContext) {
  return class extends Base {
    constructor(...args: any[]) {
      super(...args);
      console.log(`Created ${ctx.name}`);
    }
  };
}

@Logged
class Service {
  greet() { return 'hello'; }
}

new Service(); // logs: 'Created Service'

Method decorator

A method decorator uses addInitializer to auto-bind the method so it keeps the correct this when destructured.

Example Β· ts
function Bound(_: unknown, ctx: ClassMethodDecoratorContext) {
  ctx.addInitializer(function (this: any) {
    this[ctx.name] = this[ctx.name].bind(this);
  });
}

class Timer {
  private count = 0;

  @Bound
  tick(): void { this.count++; console.log(this.count); }
}

const { tick } = new Timer();
tick(); // 'this' is correctly bound

Accessor decorator

An accessor decorator intercepts the setter to clamp the assigned value between min and max without modifying the class body.

Example Β· ts
function Clamp(min: number, max: number) {
  return function(_: unknown, ctx: ClassAccessorDecoratorContext) {
    return {
      set(this: any, value: number) {
        ctx.access.set(this, Math.min(Math.max(value, min), max));
      },
    };
  };
}

class Slider {
  @Clamp(0, 100)
  accessor value = 50;
}

const s = new Slider();
s.value = 150;
console.log(s.value); // 100 (clamped)

Discussion

  • Be the first to comment on this lesson.
Standard Decorators (TS 5) β€” TypeScript | SoundsCode