Decorators
TypeScript Programming
Decorators
Decorators are a powerful metaprogramming feature that allows you to annotate and modify classes, methods, properties, and parameters using a declarative syntax. A decorator is a special kind of declaration that can be attached to a class element using the @ symbol. Decorators are widely used in frameworks like Angular, NestJS, and TypeORM to define routes, inject dependencies, validate data, and map objects to database tables. TypeScript 5.0 introduced the TC39 Stage 3 decorator specification, which differs from the older experimental decorator implementation.
Enabling Decorators
To use the classic (experimental) decorators that most frameworks currently rely on, enable the experimentalDecorators option in your tsconfig.json. TypeScript 5.0+ also supports the newer TC39 decorators without this flag.
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020"
}
}
Class Decorators
A class decorator is a function that receives the class constructor and can modify or replace it. Class decorators are applied at class declaration time.
// Simple logging decorator
function LogClass(constructor: Function) {
console.log(`Class created: ${constructor.name}`);
}
@LogClass
class UserService {
getUsers(): string[] {
return ["Alice", "Bob"];
}
}
// Console output: "Class created: UserService"
// Decorator factory — returns a decorator with configuration
function Entity(tableName: string) {
return function (constructor: Function) {
Reflect.defineMetadata("tableName", tableName, constructor);
console.log(`Entity "${constructor.name}" mapped to table "${tableName}"`);
};
}
@Entity("users")
class User {
constructor(public id: number, public name: string) {}
}
Method Decorators
Method decorators receive the target object, the method name, and the property descriptor. They can wrap the original method to add behavior like logging, timing, or access control.
// Timing decorator — measures method execution time
function Timer(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = original.apply(this, args);
const elapsed = (performance.now() - start).toFixed(2);
console.log(`${propertyKey} executed in ${elapsed}ms`);
return result;
};
return descriptor;
}
class DataProcessor {
@Timer
processRecords(records: number[]): number {
let sum = 0;
for (const r of records) {
sum += r * Math.sqrt(r);
}
return sum;
}
}
const processor = new DataProcessor();
processor.processRecords(Array.from({ length: 100000 }, (_, i) => i));
// Console: "processRecords executed in 12.34ms"
// Authorization decorator
function RequireRole(role: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (this: any, ...args: any[]) {
if (this.currentUserRole !== role) {
throw new Error(`Access denied: requires "${role}" role`);
}
return original.apply(this, args);
};
};
}
class AdminPanel {
currentUserRole = "viewer";
@RequireRole("admin")
deleteAllRecords(): void {
console.log("All records deleted");
}
}
Property Decorators
Property decorators receive the target and the property name. They are commonly used for validation, serialization metadata, or dependency injection.
// Validation decorator
function MinLength(min: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newVal: string) => {
if (newVal.length < min) {
throw new Error(
`${propertyKey} must be at least ${min} characters`
);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}
class Registration {
@MinLength(3)
username: string = "";
@MinLength(8)
password: string = "";
}
const reg = new Registration();
reg.username = "Alice"; // OK
// reg.username = "Al"; // Error: username must be at least 3 characters
Tip: Decorator factories (functions that return decorators) are the most flexible pattern because they accept configuration parameters. Most real-world decorators in frameworks like Angular and NestJS are decorator factories.
Key Takeaways
- Decorators use
@decoratorsyntax to annotate and modify classes, methods, and properties. - Enable
experimentalDecoratorsintsconfig.jsonfor framework-compatible decorators. - Class decorators receive the constructor; method decorators receive the property descriptor.
- Decorator factories return decorators and accept configuration parameters.
- Decorators are widely used in Angular, NestJS, and TypeORM for dependency injection, routing, and validation.