Decorators

Functions attached with an @ sign to classes and their members, which extend or change their behavior.

Returns
For a class, method, and accessor: a member that replaces the original, or nothing (no change). A field decorator can return a function that transforms the initial value.
On this page

Example

#
TypeScript
function logged<This, Args extends unknown[], Result>(
  method: (this: This, ...args: Args) => Result,
  context: ClassMethodDecoratorContext<This>
) {
  const name = String(context.name)
  return function (this: This, ...args: Args): Result {
    console.log(`LOG: ${name}(${args.join(', ')})`)
    return method.apply(this, args)
  }
}

class ScoreBoard {
  private total = 0

  @logged
  add(points: number) {
    this.total += points
    return this.total
  }
}

const board = new ScoreBoard()
board.add(20)
console.log(board.add(15))
Result
LOG: add(20)
LOG: add(15)
35

Definition and usage

#

A decorator is an ordinary function that you attach with an @ sign to a class or one of its members, for example @logged above a method. It runs once, at the moment the class is defined, and receives the decorated member plus a context object describing it. If it returns a new function, that function replaces the original method. Logic you write once, for example logging, timing, or a permission check, can then be attached to any method with a single line.

Since version 5.0, TypeScript supports the standard decorators from the TC39 proposal, with no extra option needed. You can use them on classes, methods, getters and setters, fields, and fields with the accessor modifier. They don’t work on plain functions or on parameters, where the compiler reports “Decorators are not valid here.” With a target lower than ESNext, the compiler turns decorators into plain JavaScript with helper functions.

Many popular libraries, for example NestJS and TypeORM, use an older, experimental version of decorators, turned on with the experimentalDecorators option. Decorators like that receive different arguments (the target object, the property name, and a descriptor), can mark parameters, and work together with the emitDecoratorMetadata option. The two versions are incompatible: a decorator written for one won’t work in the other.

Syntax

#
Syntax
function decorator(value, context) {
  return replacement // optional
}

@decorator
class SomeClass {
  @decorator method() { 
 }
  @decorator field = 1
}

Decorator arguments

#
  • value

    depends on the member

    The decorated member: a class, method, getter, or setter, and for an accessor field, an object with get and set. For plain fields, it’s undefined, because at the moment the class is defined, the field doesn’t have a value yet.
  • context

    ClassMethodDecoratorContext and related types

    A description of the member: kind (the type, for example "method" or "field"), name, static, private, and the addInitializer() method, which registers code to run during initialization.

More examples

#
A decorator with arguments
TypeScript
function clamp(min: number, max: number) {
  return function (_value: undefined, _context: ClassFieldDecoratorContext<unknown, number>) {
    return (initial: number) => Math.min(max, Math.max(min, initial))
  }
}

class ReaderSettings {
  @clamp(12, 24) fontSize = 40
  @clamp(1, 3) columns = 0
}

console.log(new ReaderSettings())
Result
ReaderSettings { fontSize: 24, columns: 1 }
A class decorator
TypeScript
const registry: string[] = []

function register(_value: new (...args: any[]) => object, context: ClassDecoratorContext) {
  registry.push(String(context.name))
}

@register
class QuizTask {}

@register
class CodeTask {}

console.log(registry)
Result
['QuizTask', 'CodeTask']

Good practices

#
  • A decorator with arguments, for example @clamp(12, 24), is a function that returns the actual decorator. Most decorators from libraries are built that way.
  • A field decorator only changes the initial value. To control every later write, mark the field with the accessor keyword and use a decorator that replaces its get and set.
  • Check that tsconfig.json doesn’t have experimentalDecorators: true. If it does, the old signatures apply, and the examples on this page won’t compile.

Related entries

#

Spotted a mistake or missing an example? Write to us.