Decorators
Two decorators eras — use the new one
Section titled “Two decorators eras — use the new one”A decorator is a function that wraps or observes a class or a class member, letting you add behavior declaratively with @name syntax. There are two incompatible designs, and it matters which you mean:
- Legacy decorators — the old, experimental design enabled by
experimentalDecoratorsintsconfig. Used heavily by older Angular and NestJS. Different signatures, relies onreflect-metadata. - Stage-3 decorators — the standardized ECMAScript design, supported natively since TypeScript 5.0 with no special flag. This is the one to learn for new code.
This lesson covers stage-3 decorators.
A method decorator
Section titled “A method decorator”A method decorator receives the original method and a context object, and returns a replacement (or nothing). Here is a @logged decorator that wraps a method to log its calls:
function logged(originalMethod: any, context: ClassMethodDecoratorContext) { const name = String(context.name); function replacement(this: any, ...args: any[]) { console.log(`calling ${name}`); return originalMethod.call(this, ...args); } return replacement;}
class Service { @logged fetch(id: number) { return `item ${id}`; }}
new Service().fetch(1); // logs "calling fetch", then returns "item 1"The context object is the heart of the new design. It tells the decorator what it is decorating — context.kind ("method", "field", "getter", …), context.name, context.static, context.private, and an addInitializer hook for setup work.
The four things you can decorate
Section titled “The four things you can decorate”| Decorator kind | Receives | Typical use |
|---|---|---|
| Class | the class, context | register it, wrap the constructor |
| Method | the method, context | wrap/replace behavior (logging, timing) |
| Accessor | the getter/setter, context | intercept reads/writes |
| Field | undefined, context | return an initializer to transform the initial value |
A field decorator is a little different — it does not receive a value to wrap; it can return an initializer function that transforms the field’s initial value when an instance is created.
Decorator factories
Section titled “Decorator factories”To make a decorator configurable, write a function that returns a decorator — a decorator factory. The @name(args) call runs the factory, and its return value is the actual decorator.
function logged(label: string) { // factory: takes config return function (originalMethod: any, context: ClassMethodDecoratorContext) { return function (this: any, ...args: any[]) { console.log(`[${label}] ${String(context.name)}`); return originalMethod.call(this, ...args); }; };}
class Api { @logged("http") get() { /* ... */ }}