TypeScript: TypeScript Decorators
Last updated: 2026-08-26
Decorators are an experimental syntax—they use the @decorator syntax to add metadata to classes, methods, and properties, or to modify their behavior. They are widely used in frameworks such as NestJS and Angular.
1. Overview of Decorators
(1) What Is a Decorator?
A decorator is a function—it takes a target (class, method, or property) as an argument and can modify or enhance the target’s behavior:
// Decorator Functions
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Call ${key},Parameters:${args}`);
return original.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
let calc = new Calculator();
calc.add(1, 2);
// Output:Call add,Parameters:1,2
(2) Enable decorator support
Decorators are an experimental feature and must be enabled in tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
(3) Five Types of Decorators
| Type | Modified Target | Parameters |
|---|---|---|
| Class Decorator | Class Definition | Constructor |
| Method Decorator | Class Method | Target, Method Name, Descriptor |
| Property Decorator | Class Property | Target, Property Name |
| Parameter Decorator | Function Parameter | Target, Method Name, Parameter Index |
| Accessor Decorator | getter/setter | target, accessor name, descriptor |
2. Class Decorators
A class decorator takes a constructor as an argument and can modify or replace the class definition:
(1) Basic Usage
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Config {
host: string = "localhost";
port: number = 3000;
}
// Config Has been sealed——New properties cannot be added
(2) Decorator Factory
When parameters are required, use a factory function to return a decorator:
function className(prefix: string) {
return function (constructor: Function) {
constructor.prototype._displayName = `${prefix}_${constructor.name}`;
};
}
@className("App")
class UserService {}
// UserService.prototype._displayName = "App_UserService"
(3) Overriding the Constructor
function logged<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
console.log(`Create ${constructor.name},Parameters:${args}`);
super(...args);
}
};
}
@logged
class User {
constructor(public name: string, public age: number) {}
}
let user = new User("Charlie", 20);
// Output:Create User,Parameters:Charlie,20
▶ Example: Registration Decorator—Automatic Class Collection
Output:
User1
Products2
const registry: Map<string, any> = new Map();
function Register(name: string) {
return function <T extends { new (...args: any[]): {} }>(constructor: T) {
registry.set(name, constructor);
return constructor;
};
}
@register("user")
class UserService {
getUser(id: number) { return { id, name: "User" + id }; }
}
@register("product")
class ProductService {
getProduct(id: number) { return { id, title: "Products" + id }; }
}
// Get a Service by Name
function getService(name: string) {
let Service = registry.get(name);
if (!Service) throw new Error(`Unregistered Services:${name}`);
return new Service();
}
let user = (getService("user") as UserService).getUser(1);
let product = (getService("product") as ProductService).getProduct(2);
console.log(user.name); // "User1"
console.log(product.title); // "Products2"
Output:
User1
Products2
3. Method Decorators
Method decorators can observe, modify, or replace method definitions:
(1) Basic Usage
function enumerable(value: boolean) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Person {
constructor(public name: string) {}
@enumerable(false)
getFullName(): string {
return this.name;
}
}
(2) Method Execution Log
function measure(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = performance.now();
const result = original.apply(this, args);
const end = performance.now();
console.log(`${key} Execution Time:${(end - start).toFixed(2)}ms`);
return result;
};
return descriptor;
}
class DataProcessor {
@measure
processLargeArray(data: number[]): number {
return data.reduce((sum, n) => sum + n, 0);
}
}
let processor = new DataProcessor();
processor.processLargeArray(Array.from({ length: 1000000 }, (_, i) => i));
// Output:processLargeArray Execution Time:XX.XXms
(3) Method Anti-Jitter
function debounce(delay: number) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
let timer: any;
descriptor.value = function (...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => original.apply(this, args), delay);
};
return descriptor;
};
}
class SearchBox {
@debounce(300)
search(query: string): void {
console.log(`Search:${query}`);
}
}
let box = new SearchBox();
box.search("T"); // 300ms Then, if there is no new input, execute it
box.search("Ty");
box.search("Typ");
box.search("TypeScript"); // This will be executed only once
4. Property Decorators and Parameter Decorators
(1) Property Decorators
function format(formatStr: string) {
return function (target: any, key: string) {
// Store formatting information in metadata
Reflect.defineMetadata("format", formatStr, target, key);
};
}
class User {
@format("YYYY-MM-DD")
birthday: string = "2000-01-15";
@format("HH:mm:ss")
loginTime: string = "09:30:00";
}
(2) Parameter Decorators
function required(target: any, key: string, index: number) {
const existing: number[] = Reflect.getMetadata("required", target, key) || [];
existing.push(index);
Reflect.defineMetadata("required", existing, target, key);
}
class UserService {
createUser(@required name: string, @required email: string, age?: number) {
// Parameter validation is handled by the framework at runtime.
}
}
5. Combining Decorators
Multiple decorators can be used at the same time—they are executed in order from bottom to top (the ones closest to the target are executed first):
function log1(target: any, key: string, descriptor: PropertyDescriptor) {
console.log("log1 Applications");
}
function log2(target: any, key: string, descriptor: PropertyDescriptor) {
console.log("log2 Applications");
}
class Example {
@log1 // The Second Application(Outer layer)
@log2 // The First Application(Inner layer,Approach Methods)
method() {}
}
// Output:log2 Applications → log1 Applications
(1) Common Combination Patterns
class ApiController {
@logged
@measure
@debounce(100)
async fetchData(url: string): Promise<any> {
// First debounce → then measure → then logged(Bottom-up application)
}
}
▶ Example: Readonly Method Decorator
Output:
User1
Products2
function readonly(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
return descriptor;
}
class Config {
@readonly
getVersion(): string { return "1.0.0"; }
}
let cfg = new Config();
// cfg.getVersion = () => "2.0.0"; // TypeError: Cannot assign to read-only property
Output:
User1
Products2
▶ Example: Deprecated Method Warning
Output:
User1
Products2
function deprecated(message: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.warn(`${key} is deprecated: ${message}`);
return original.apply(this, args);
};
return descriptor;
};
}
class LegacyService {
@deprecated("Use fetchUsers() instead")
getUsers(): string[] { return ["Alice", "Bob"]; }
fetchUsers(): string[] { return ["Alice", "Bob"]; }
}
let svc = new LegacyService();
svc.getUsers(); // getUsers is deprecated: Use fetchUsers() instead
Output:
User1
Products2
❓ FAQ
experimentalDecorators), but the old syntax is still available. Frameworks such as NestJS and Angular currently use the old syntax. It is recommended to follow the guidelines provided by your framework.@log method() is equivalent to method = log(method). Without decorators, you can achieve the same result using higher-order functions, the mixin pattern, AOP libraries, and other methods. The advantage of decorators is that they are declarative, intuitive, and result in cleaner code.📖 Summary
- Decorators use the
@decoratorsyntax to add metadata to classes and class members or modify their behavior - Five types of decorators: classes, methods, properties, parameters, and accessors—each with a different parameter signature
- The decorator factory returns a decorator function—allowing the decorator to accept parameters
- Class decorators can replace constructors (returning a new class), while method decorators can wrap methods
- Multiple decorators are applied from bottom to top (those closest to the target are executed first)
- Decorators are an experimental feature that requires the
experimentalDecoratorsoption
📝 Exercises
- Basic Exercise (Difficulty ⭐): Write a
@readonlymethod decorator that makes the method non-overridable (writable: false). Apply it to a method in a class, then try to override that method in a subclass and observe the result. - Advanced Problem (Difficulty ⭐⭐): Write a
@deprecated(message)decorator factory that prints a deprecation warning when the decorated method is called. Hint: Addconsole.warnto the method wrapper function. - Challenge (Difficulty: ⭐⭐⭐): Implement simple dependency injection using a class decorator—mark the service class with
@Injectable(), mark the dependency property with@Inject(Service), andContainer.resolve(TargetClass)automatically resolves and injects the dependency.