| |

TypeScript 77 ๐Ÿ”ท Decorators โ€” Legacy Experimental

Decorators are a language feature that attaches metadata or behavior to classes, methods, properties, and parameters. TypeScript has had two versions of this feature: the legacy experimental version, which required the experimentalDecorators compiler flag and was never standardized, and the standard Stage 3 version, which TypeScript 5.0 shipped as the default. The legacy version is the one this chapter covers. It is the version that NestJS, TypeORM, Angular (through v16), and class-validator depend on, and it is the version most production code still uses. Understanding it means understanding the runtime behavior, the compiler flags, the metadata system that powers dependency injection, and the specific syntax that the Stage 3 decorators replaced.

Key point: Legacy decorators require "experimentalDecorators": true in tsconfig.json. They are applied with the syntax @expression, evaluated bottom-up for each declaration, and called with either three arguments (method, accessor decorators: target, propertyKey, descriptor) or two arguments (property decorators: target, propertyKey) . Parameter decorators receive three arguments: target, propertyKey, parameterIndex . The emitDecoratorMetadata flag emits runtime type metadata (design:type, design:paramtypes, design:returntype) using the reflect-metadata library, which is what allows dependency injection frameworks to resolve constructor parameter types .


Enabling legacy decorators

Legacy decorators are disabled by default. The compiler flag must be set, either on the command line or in tsconfig.json.

{
  "compilerOptions": {
    "target": "ES2022",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

The experimentalDecorators flag enables the syntax and the runtime emission. The emitDecoratorMetadata flag enables the metadata reflection, which requires the reflect-metadata package to be installed and imported at the application entry point . Without the import, Reflect.getMetadata is undefined and the metadata calls fail at runtime.

Why the flag exists. The decorator proposal was never finalized in the form TypeScript implemented. The experimentalDecorators flag is a marker that the feature is not stable and may change. TypeScript 5.0 shipped the Stage 3 proposal as the default, and the legacy version is now the opt-in behavior .

Why emitDecoratorMetadata matters for frameworks. NestJS, TypeORM, and class-validator rely on the metadata to know what types a constructor’s parameters have. When @Injectable() is applied to a class, the compiler emits metadata that says “the first parameter is of type UserService.” The DI container reads this metadata and resolves the dependency . Without emitDecoratorMetadata, the metadata is not emitted, and the framework cannot resolve the dependencies.

Why the metadata is controversial. The metadata emission changes the runtime code based on type information, which violates TypeScript’s design goal of types being erased and having no runtime effect . The types are not available at runtime, so the compiler has to synthesize the metadata at compile time, and the emitted code depends on what the compiler inferred. This is why the Stage 3 decorators removed the metadata feature entirely.


Method decorators

A method decorator is applied to a method and receives three arguments: the target (the prototype for an instance method, the constructor for a static method), the property key, and the property descriptor .

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor): void {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

The log decorator replaces the method’s value with a wrapper that logs and delegates. The @log syntax applies the decorator at class definition time, and the wrapper runs on every call .

Why the descriptor is the mechanism. The method decorator receives the PropertyDescriptor, which is the same object that Object.defineProperty uses. The decorator can modify value, enumerable, configurable, and writable. This is how logging, timing, memoization, and validation wrappers are implemented .

Why the return value matters. If the decorator returns a value, that value is used as the new PropertyDescriptor. Returning undefined means the original descriptor (possibly modified in place) is used . The common pattern is to mutate the descriptor in place and return nothing, or to return a new descriptor.

Why the any types are the legacy signature. The legacy decorator signature uses any for the target and string for the property key. There is no type safety at the decorator boundary. The Stage 3 decorators replaced this with a typed context object .


Property decorators

A property decorator is applied to a property and receives two arguments: the target and the property key. It does not receive a property descriptor .

function format(formatString: string) {
  return function (target: any, propertyKey: string): void {
    Reflect.defineMetadata("format", formatString, target, propertyKey);
  };
}

class Greeter {
  @format("Hello, %s")
  greeting: string;

  greet(): string {
    const formatString = Reflect.getMetadata("format", this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

The format factory returns a property decorator that stores metadata on the class prototype. The greet method reads the metadata at runtime and uses it .

Why property decorators cannot modify behavior directly. There is no property descriptor for an instance property at class definition time. The property is defined on the instance, not the prototype, and the decorator runs before any instance exists. The decorator can only observe that a property of that name has been declared, and it can store metadata. Behavior modification requires Object.defineProperty at a later point, or a getter/setter .

Why property decorators are mostly for metadata. Because they cannot intercept reads or writes directly, their primary use is storing metadata that other code reads. Dependency injection frameworks use property decorators for @Inject() and similar markers, and validation frameworks use them for @IsEmail() and similar constraints .

Why the metadata reflection matters. The Reflect.defineMetadata and Reflect.getMetadata calls require reflect-metadata to be imported. Without it, the methods are undefined and the decorator throws at runtime .


Accessor decorators

An accessor decorator is applied to a getter or setter. It receives three arguments, like a method decorator, because accessors have property descriptors .

function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): void {
    descriptor.configurable = value;
  };
}

class Point {
  private _x: number = 0;

  @configurable(false)
  get x(): number {
    return this._x;
  }
}

The configurable decorator modifies the descriptor’s configurable flag. The decorator is applied to the first accessor in document order if both a getter and a setter exist for the same member .

Why only one accessor is decorated. TypeScript applies the decorator to the combined descriptor for the property, not to the getter and setter separately. If both are declared, the decorators must be applied to the first one in source order .


Parameter decorators

A parameter decorator is applied to a constructor parameter or a method parameter. It receives three arguments: the target, the property key (or undefined for a constructor), and the parameter index .

function inject(token: string) {
  return function (target: any, propertyKey: string | undefined, parameterIndex: number): void {
    const existing = Reflect.getOwnMetadata("inject", target, propertyKey) || {};
    existing[parameterIndex] = token;
    Reflect.defineMetadata("inject", existing, target, propertyKey);
  };
}

class UserService {
  constructor(@inject("Database") private db: any) {}
}

The inject factory returns a parameter decorator that stores the token under the parameter index. At runtime, the DI container reads this metadata and resolves the parameter .

Why parameter decorators are the DI backbone. NestJS and Angular (legacy) use parameter decorators to mark constructor parameters for injection. The decorator stores the token, and the framework reads the metadata to know what to inject . The emitDecoratorMetadata flag provides the type information when no explicit token is given, which is why the two features are used together.

Why parameter decorators are not supported in Stage 3. The standard proposal has not included parameter decorators. This is the primary reason NestJS and TypeORM cannot migrate to Stage 3 yet .


Decorator evaluation order

When multiple decorators are applied to a single declaration, they are evaluated in a specific order: the expressions are evaluated top-down, but the resulting decorator functions are called bottom-up .

@first
@second
method() {}

first is evaluated first, then second. But second is called first, then first. The result is that first wraps second .

Why bottom-up matters. If both decorators modify the method, the one closest to the method (the bottom one) runs first, and the outer one sees the result. This is the same order as function composition: first(second(method)).

Why the order matters for frameworks. When a class has both a class decorator and a method decorator, the method decorators run first (bottom-up), then the class decorator. The class decorator sees the fully decorated class. This is why @Injectable() on a class and @Inject() on a parameter work together: the parameter decorator stores the metadata, and the class decorator reads it.


Complete Example Session

// ============================================
// PART 1: THE TSCONFIG
// ============================================

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

// ============================================
// PART 2: THE METHOD DECORATOR
// ============================================

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor): void {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey}`, args);
    return original.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

// ============================================
// PART 3: THE PROPERTY DECORATOR
// ============================================

function format(formatString: string) {
  return function (target: any, propertyKey: string): void {
    Reflect.defineMetadata("format", formatString, target, propertyKey);
  };
}

class Greeter {
  @format("Hello, %s")
  greeting: string = "World";
}

// ============================================
// PART 4: THE PARAMETER DECORATOR
// ============================================

function inject(token: string) {
  return function (target: any, propertyKey: string | undefined, parameterIndex: number): void {
    console.log(`Injecting ${token} at index ${parameterIndex}`);
  };
}

class UserService {
  constructor(@inject("Database") private db: any) {}
}

// ============================================
// PART 5: THE METADATA
// ============================================

// With emitDecoratorMetadata, the compiler emits:
// __metadata("design:type", Function)
// __metadata("design:paramtypes", [Object])
// __metadata("design:returntype", void 0)

// The metadata is readable with:
Reflect.getMetadata("design:paramtypes", UserService);

// ============================================
// PART 6: THE EVALUATION ORDER
// ============================================

// The source:
@first
@second
method() {}

// The evaluation:
// 1. first is evaluated
// 2. second is evaluated
// 3. second is called
// 4. first is called

// ============================================
// PART 7: THE TYPICAL FRAMEWORK USE
// ============================================

// NestJS-style:
@Injectable()
class UserService {
  constructor(
    @InjectRepository(User) private repo: Repository<User>,
    private logger: LoggerService,
  ) {}
}

// The @Injectable() class decorator marks the class.
// The @InjectRepository() parameter decorator marks the parameter.
// The emitDecoratorMetadata provides the type for the logger.

// ============================================
// PART 8: THE REFLECT-METADATA IMPORT
// ============================================

// main.ts
import "reflect-metadata";

// Must be imported before any decorated class is defined.

// ============================================
// PART 9: THE STAGE 3 COMPARISON
// ============================================

// Legacy (this chapter):
@log
method() {}

// Stage 3 (TypeScript 5.0+ default):
@log
method() {}

// The syntax is the same, but the signature differs:
// Legacy: (target, propertyKey, descriptor)
// Stage 3: (value, context)

// ============================================
// PART 10: WHAT NOT TO DO
// ============================================

// Don't forget the experimentalDecorators flag
@log
method() {}  // โŒ without the flag

// Don't forget the reflect-metadata import
Reflect.getMetadata(...)  // โŒ without the import

// Don't use parameter decorators with Stage 3
constructor(@inject("DB") db: any) {}  // โŒ Stage 3 does not support this

// Don't use the legacy decorators in new projects without a reason
// The Stage 3 decorators are the default.

The ten parts cover the tsconfig, the method decorator, the property decorator, the parameter decorator, the metadata, the evaluation order, the framework use, the reflect-metadata import, the Stage 3 comparison, and the anti-patterns.


Quick Reference

The Decorator Types

TypeArgumentsCan Modify Behavior
Methodtarget, propertyKey, descriptorโœ…
Accessortarget, propertyKey, descriptorโœ…
Propertytarget, propertyKeyโŒ (metadata only)
Parametertarget, propertyKey, parameterIndexโŒ (metadata only)
Classconstructorโœ…

The Compiler Flags

FlagPurpose
experimentalDecoratorsEnable legacy decorators
emitDecoratorMetadataEmit runtime type metadata
reflect-metadataThe runtime library (npm)

The Metadata Keys

KeyPurpose
design:typeThe property’s type
design:paramtypesThe method’s parameter types
design:returntypeThe method’s return type

The Frameworks Using Legacy Decorators

FrameworkUse
NestJS@Injectable(), @Controller(), @Inject()
TypeORM@Entity(), @Column(), @ManyToOne()
Angular (pre-v16)@Component(), @Injectable(), @Input()
class-validator@IsEmail(), @MinLength()

The Evaluation Order

StepAction
1Top-down expression evaluation
2Bottom-up decorator application
3Class decorator runs last

Best Practices

โœ… Do This:

// Enable both flags for legacy decorators
{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}                                                              // โœ…
// Import reflect-metadata at the entry point
import "reflect-metadata";                                     // โœ…
// Use decorator factories for configurable decorators
function log(prefix: string) { return (target: any, key: string, desc: PropertyDescriptor) => { ... }; } // โœ…
// Keep decorators focused on one responsibility
function log(target: any, key: string, desc: PropertyDescriptor) { ... } // โœ…
// Use parameter decorators for DI in legacy frameworks
constructor(@inject("Database") private db: any) {}            // โœ…

โŒ Don’t Do This:

// Don't use decorators without the flag
@log
method() {}  // โŒ the experimentalDecorators flag is required     // โš ๏ธ
// Don't forget the reflect-metadata import
Reflect.getMetadata(...)  // โŒ the library is not imported        // โš ๏ธ
// Don't use parameter decorators with Stage 3
constructor(@inject("DB") db: any) {}  // โŒ the Stage 3 does not support it // โš ๏ธ
// Don't use the legacy decorators in new projects without a reason
// The Stage 3 decorators are the default.                      // โš ๏ธ
// Don't rely on the metadata for the complex types
// The emitted metadata is limited to the design types.        // โš ๏ธ

Common Pitfalls

PitfallProblemSolution
Missing experimentalDecoratorsThe syntax errorThe flag
Missing emitDecoratorMetadataThe DI failsThe flag
Missing reflect-metadata importThe runtime errorThe import
The parameter decorator in Stage 3The errorThe legacy
The circular dependencyThe metadata resolution failsThe forwardRef
The property decorator cannot modifyThe no-effectThe metadata
The evaluation order confusionThe wrong wrapThe bottom-up

Real-World Examples

1. The @Injectable()

@Injectable()
class UserService {}

2. The @Inject()

constructor(@Inject("Database") private db: Database) {}

3. The @Entity()

@Entity()
class User {
  @PrimaryKey()
  id!: number;

  @Column()
  name!: string;
}

4. The @Component()

@Component({ selector: 'app-root' })
class AppComponent {}

5. The @Input()

@Input() name: string;

6. The @IsEmail()

@IsEmail()
email: string;

7. The custom method decorator

function log(target: any, key: string, desc: PropertyDescriptor) { ... }

8. The custom property decorator

function format(fmt: string) { return (target: any, key: string) => { ... }; }

9. The custom parameter decorator

function inject(token: string) { return (target: any, key: string, index: number) => { ... }; }

10. The reflect-metadata import

import "reflect-metadata";

Visual: The Decorator Types and Their Arguments

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  METHOD DECORATOR                            โ”‚
โ”‚    @log                                      โ”‚
โ”‚    method() {}                               โ”‚
โ”‚                                              โ”‚
โ”‚    (target, propertyKey, descriptor)         โ”‚
โ”‚    โ†’ Can modify behavior                     โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  PROPERTY DECORATOR                          โ”‚
โ”‚    @format("Hello, %s")                      โ”‚
โ”‚    greeting: string;                         โ”‚
โ”‚                                              โ”‚
โ”‚    (target, propertyKey)                     โ”‚
โ”‚    โ†’ Metadata only                           โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  PARAMETER DECORATOR                         โ”‚
โ”‚    constructor(@inject("DB") db) {}          โ”‚
โ”‚                                              โ”‚
โ”‚    (target, propertyKey, parameterIndex)     โ”‚
โ”‚    โ†’ Metadata only                           โ”‚
โ”‚                                              โ”‚
โ”‚  The legacy signature uses any/string.       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Evaluation Order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SOURCE:                                 โ”‚
โ”‚    @first                                    โ”‚
โ”‚    @second                                   โ”‚
โ”‚    method() {}                               โ”‚
โ”‚                                              โ”‚
โ”‚  THE EVALUATION:                             โ”‚
โ”‚    1. first is evaluated (top-down)          โ”‚
โ”‚    2. second is evaluated                    โ”‚
โ”‚                                              โ”‚
โ”‚  THE APPLICATION:                            โ”‚
โ”‚    3. second is called (bottom-up)           โ”‚
โ”‚    4. first is called                        โ”‚
โ”‚                                              โ”‚
โ”‚  THE RESULT:                                 โ”‚
โ”‚    first(second(method))                     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Metadata Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SOURCE:                                 โ”‚
โ”‚    @Injectable()                             โ”‚
โ”‚    class UserService {                       โ”‚
โ”‚      constructor(private logger: Logger) {}  โ”‚
โ”‚    }                                         โ”‚
โ”‚                                              โ”‚
โ”‚         โ”‚  The compiler                      โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚                                              โ”‚
โ”‚  THE EMITTED:                                โ”‚
โ”‚    __metadata("design:paramtypes", [Logger]) โ”‚
โ”‚                                              โ”‚
โ”‚         โ”‚  The runtime                       โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚                                              โ”‚
โ”‚  THE DI CONTAINER:                           โ”‚
โ”‚    Reflect.getMetadata(...)                  โ”‚
โ”‚    โ†’ resolves the Logger                     โ”‚
โ”‚                                              โ”‚
โ”‚  The metadata bridges the compile-time's     โ”‚
โ”‚  types and the runtime's DI.                 โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Legacy vs the Stage 3

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE LEGACY (the experimental)               โ”‚
โ”‚    (target, propertyKey, descriptor)         โ”‚
โ”‚    The parameter decorators's supported      โ”‚
โ”‚    The emitDecoratorMetadata's supported     โ”‚
โ”‚    The experimentalDecorators flag           โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  THE STAGE 3 (the modern)                    โ”‚
โ”‚    (value, context)                          โ”‚
โ”‚    The parameter decorators's NOT supported  โ”‚
โ”‚    The metadata's NOT supported              โ”‚
โ”‚    The no flag's required                    โ”‚
โ”‚                                              โ”‚
โ”‚  The legacy's is the framework's, and the    โ”‚
โ”‚  Stage 3's is the future's.                  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Framework Dependencies

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  NestJS / TypeORM / class-validator          โ”‚
โ”‚    The legacy decorators's                   โ”‚
โ”‚    The emitDecoratorMetadata's               โ”‚
โ”‚    The reflect-metadata's                    โ”‚
โ”‚    The parameter decorators's                โ”‚
โ”‚                                              โ”‚
โ”‚  THE MIGRATION'S BLOCKER:                    โ”‚
โ”‚    The Stage 3's has no parameter decorators.โ”‚
โ”‚    The Stage 3's has no metadata's.          โ”‚
โ”‚                                              โ”‚
โ”‚  The frameworks's cannot migrate yet.        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
The flagexperimentalDecorators: true
The metadata flagemitDecoratorMetadata: true
The libraryreflect-metadata
The method’s args(target, propertyKey, descriptor)
The property’s args(target, propertyKey)
The parameter’s args(target, propertyKey, parameterIndex)
The evaluationThe bottom-up
The frameworkNestJS, TypeORM, Angular
The Stage 3The default in TS 5.0+
The parameter decoratorsThe legacy only

Key takeaways:

  • The legacy decorators require the experimentalDecorators flag โ€” the syntax is disabled by default, and the flag enables both the parsing and the runtime emission
  • The emitDecoratorMetadata flag emits runtime type metadata โ€” the design:type, the design:paramtypes, and the design:returntype are the three keys, and the reflect-metadata package is required
  • The method and accessor decorators receive three arguments โ€” the target, the propertyKey, and the descriptor, and they can modify the behavior
  • The property decorators receive two arguments โ€” the target and the propertyKey, and they can only store metadata, not modify behavior
  • The parameter decorators receive three arguments โ€” the target, the propertyKey, and the parameterIndex, and they are the DI framework’s backbone
  • The decorators are evaluated top-down and applied bottom-up โ€” the bottom decorator runs first, and the top decorator sees the result
  • The legacy decorators are the framework’s requirement โ€” the NestJS, the TypeORM, the class-validator depend on the parameter decorators and the metadata, and the Stage 3 does not support them
  • The Stage 3 decorators are the default in TypeScript 5.0+ โ€” the (value, context) signature, the no flags, the no metadata, and the no parameter decorators
  • The reflect-metadata import is mandatory โ€” the Reflect.getMetadata and the Reflect.defineMetadata are undefined without it, and the decorated class throws at runtime
  • The metadata emission changes the runtime behavior based on type information โ€” this is the reason the Stage 3 removed the feature, and it is the legacy’s fundamental’s tradeoff

Remember: The legacy experimental decorators are the TypeScript’s pre-standard’s feature, and they are the framework’s dependency’s. The experimentalDecorators and the emitDecoratorMetadata are the flags, and the reflect-metadata is the library. The method’s and the property’s and the parameter’s decorators have the specific’s signatures. The evaluation’s is the bottom-up, and the metadata’s is the DI’s. The Stage 3’s is the future’s, but the legacy’s is the present’s for the NestJS and the TypeORM.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!