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:

TYPESCRIPT
// 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:

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

TYPESCRIPT
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:

TYPESCRIPT
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

TYPESCRIPT
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:

TEXT 📖 Display only
User1
Products2
TYPESCRIPT
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:

TEXT 📖 Display only
User1
Products2


3. Method Decorators

Method decorators can observe, modify, or replace method definitions:

(1) Basic Usage

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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):

TYPESCRIPT
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

TYPESCRIPT
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:

TEXT 📖 Display only
User1
Products2
TYPESCRIPT
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:

TEXT 📖 Display only
User1
Products2

▶ Example: Deprecated Method Warning

Output:

TEXT 📖 Display only
User1
Products2
TYPESCRIPT
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:

TEXT 📖 Display only
User1
Products2

❓ FAQ

Q Are decorators a stable feature?
A They are currently an experimental feature (Stage 3 proposal). Starting with TypeScript 5.0, the new Stage 3 decorator syntax is supported (no longer requiring 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.
Q Can decorators be used with functions?
A No. Decorators can only be used with classes and class members (methods, properties, and parameters). Regular functions do not support decorators—this is a design limitation of the JavaScript proposal. If you need to enhance a function, use the higher-order function (wrapper) pattern.
Q What is the performance impact of decorators?
A Decorators are executed once when the class is defined (not on every call), so the overhead is minimal. Method decorators that modify the wrapped function incur a slight overhead on each call (an additional function call), but this is usually negligible. In performance-sensitive scenarios, avoid using multiple layers of decorators on hot paths.
Q Can the same effect be achieved without using decorators?
A Yes. Decorators are essentially syntactic sugar—@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

📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Write a @readonly method 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.
  2. Advanced Problem (Difficulty ⭐⭐): Write a @deprecated(message) decorator factory that prints a deprecation warning when the decorated method is called. Hint: Add console.warn to the method wrapper function.
  3. Challenge (Difficulty: ⭐⭐⭐): Implement simple dependency injection using a class decorator—mark the service class with @Injectable(), mark the dependency property with @Inject(Service), and Container.resolve(TargetClass) automatically resolves and injects the dependency.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏