Featured image of post How to Use Standard Decorators in TypeScript Featured image of post How to Use Standard Decorators in TypeScript

How to Use Standard Decorators in TypeScript

Learn how to write and configure ECMAScript standard decorators in TypeScript 5.0 without relying on legacy experimental flags.

Introduction

In TypeScript, decorators have long been a key feature, popularized by major frameworks like Angular, NestJS, and TypeORM to manage metadata and dependency injection.

However, for years, decorators remained an experimental feature (requiring the experimentalDecorators flag), relying on a legacy proposal that differed from the evolving ECMAScript (JavaScript) standard.

With the release of TypeScript 5.0, official support for the standard ECMAScript Decorators (Stage 3/4) was introduced. This brings improved type safety and runtime consistency to the decorator syntax. This article explains how to write standard decorators in modern TypeScript.


1. Experimental vs. Standard Decorators

The primary difference lies in the configuration requirements and runtime API signature of the decorator functions.

Configuration Changes

  • Legacy (Experimental): Required "experimentalDecorators": true inside your tsconfig.json.
  • Standard (New): Runs out-of-the-box by default. If compile targets support the latest ECMAScript features, standard decorators transpile natively.

Context Objects and Type Safety

Legacy decorators had access to loose property descriptors, making type safety difficult to enforce. Standard decorators receive a structured context object as their second argument, providing type-safe metadata about the decorated target (such as its name, visibility, and target kind).


2. Writing a Standard Method Decorator

Let’s write a standard logging decorator that prints messages to the console whenever a class method is executed.

Decorator Definition

// Standard method decorator signature
function logMethod<This, Args extends any[], Return>(
  target: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
  const methodName = String(context.name);

  // Return a wrapped replacement function
  return function (this: This, ...args: Args): Return {
    console.log(`[LOG] Calling ${methodName} with arguments:`, args);
    const result = target.apply(this, args);
    console.log(`[LOG] Finished ${methodName}. Returned:`, result);
    return result;
  };
}

Applying the Decorator

class UserService {
  @logMethod
  getUser(id: number) {
    return { id, name: "Takao" };
  }
}

const service = new UserService();
service.getUser(42); // Prints log messages during execution
  • ClassMethodDecoratorContext: Provides access to target metadata, such as whether the method is static, private, or what name it holds (context.name), ensuring type safety at compile time.

3. Key Migration Caveats

While standard decorators improve type safety, migrating legacy codebases requires caution:

  1. Reflect Metadata Interoperability: Legacy decorators often relied on the reflect-metadata library to runtime-inject dependencies (e.g., in NestJS). The standard decorator proposal implements a different metadata model. Verify framework compatibility before disabling experimental flags.
  2. Deprecation of Parameter Decorators: Decorators applied directly to method arguments (e.g., updateUser(@Inject() id: number)) were excluded from the final ECMAScript Stage 3 standard. Standard decorators can only target classes, methods, fields, and accessors.

Conclusion

The standardization of decorators in TypeScript 5.0 aligns the language with the future of JavaScript.

  1. Use decorators natively without configuring the experimentalDecorators flag.
  2. Leverage the context parameter to query target metadata safely.
  3. Be mindful of parameter decorator deprecations when refactoring legacy codebases.

Adopting standard decorators helps ensure your metadata-driven TypeScript code remains compatible with the evolving JavaScript standard.