TypeScript Decorators

Decorators attach reusable behavior to classes, methods, and properties by wrapping them with a special function at definition time.

#Jobseeker

Find matching jobs & Auto apply with AI

Hyring
Jobseeker using Hyring to search, apply, and prepare with AI

Enabling Experimental Decorators

Decorators are still an experimental TypeScript feature (distinct from the newer TC39 stage-3 decorators shipped by default in modern TypeScript). To use the classic syntax shown here, enable `"experimentalDecorators": true` in `tsconfig.json`, and add `"emitDecoratorMetadata": true` if you rely on frameworks like Angular or NestJS that read parameter types at runtime.

Class Decorators

A class decorator is a function that receives the constructor and runs once, right when the class is defined - not when an instance is created. It can log, register, or even replace the constructor entirely by returning a new one.

A Simple Class Decorator

function Logger(constructor: Function): void {
  console.log(`Creating instance of ${constructor.name}`);
}

@Logger
class UserService {
  constructor(public name: string) {}
}

new UserService("Ada");

Method Decorators

A method decorator receives the class prototype, the method name, and a `PropertyDescriptor`. Replacing `descriptor.value` lets you wrap the original method - a common use is measuring execution time or adding logging without touching the method body itself.

Timing a Method Call

function LogExecutionTime(
  target: unknown,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const original = descriptor.value;
  descriptor.value = function (this: unknown, ...args: unknown[]) {
    const start = Date.now();
    const result = original.apply(this, args);
    console.log(`${propertyKey} took ${Date.now() - start}ms`);
    return result;
  };
  return descriptor;
}

class Report {
  @LogExecutionTime
  generate(rows: number): string {
    let total = 0;
    for (let i = 0; i < rows; i++) total += i;
    return `Processed ${rows} rows, sum ${total}`;
  }
}

new Report().generate(1000);

Decorator Factories

A decorator factory is a function that returns a decorator, which lets you pass configuration into it. This is how framework decorators like `@Component("selector")` accept arguments while still following the plain decorator signature underneath.

A Decorator Factory

function Component(selector: string) {
  return function (constructor: Function): void {
    (constructor as unknown as { selector: string }).selector = selector;
  };
}

@Component("app-user-card")
class UserCard {
  constructor(public title: string) {}
}

console.log((UserCard as unknown as { selector: string }).selector);
  • Class decorators - applied to the constructor
  • Method decorators - applied to a method's property descriptor
  • Accessor decorators - applied to a get/set pair
  • Property decorators - applied to a class field
  • Parameter decorators - applied to a single function argument
Decorator typeApplied toParameters received
ClassClass declarationconstructor
MethodMethod definitiontarget, propertyKey, descriptor
Accessorget/set pairtarget, propertyKey, descriptor
PropertyClass fieldtarget, propertyKey
ParameterFunction argumenttarget, propertyKey, parameterIndex
Note: Stack multiple decorators on the same declaration and they run bottom-up for evaluation but top-down for the actual call - useful to remember when order matters, such as combining `@LogExecutionTime` with an `@Authorize` decorator.
Note: These experimental decorators use a different runtime shape than the TC39 stage-3 decorators TypeScript now supports without the experimental flag - mixing tutorials or library code written for one against a project configured for the other will cause confusing type errors.

Exercise: TypeScript Decorators

At what point does a class decorator function actually execute?