TypeScript Decorators
Decorators attach reusable behavior to classes, methods, and properties by wrapping them with a special function at definition time.
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
Exercise: TypeScript Decorators
At what point does a class decorator function actually execute?