Skip to content
intermediate Phase 14 · TypeScript Design Patterns

Decorator & Adapter Patterns (Advanced)

Use structural patterns for extending functionality and interface adaptation.

1h
0 problems
Topic Progress 0%

Decorators: Extending Without Editing

Decorators: Extending Without Editing

A decorator is a function that receives the decorated target and can wrap or annotate it. TypeScript 5 supports the ECMAScript standard decorators by default; "experimentalDecorators": true opts into the older legacy semantics that Angular and older NestJS code rely on:

function logged(target: Function, context: ClassMethodDecoratorContext) {
  const name = String(context.name);
  return function (this: unknown, ...args: unknown[]) {
    console.log(`${name}(`, args, ")");
    return target.apply(this, args);
  };
}

class Svc { @logged run(x: number) { return x * 2; } }

Standard decorators receive a context object with name and kind; only the legacy path supports emitDecoratorMetadata, which dependency-injection frameworks use for constructor injection.

Adapter: Fitting a Foreign Interface

Adapter: Fitting a Foreign Interface

An adapter translates an interface you cannot change into the one your code wants — third-party SDKs, legacy services, or a vendor payload with different field names:

interface Payee { charge(cents: number): Promise<{ ok: boolean }> }

class StripeAdapter implements Payee {
  constructor(private stripe: StripeSDK) {}
  async charge(cents: number) {
    const r = await this.stripe.charges.create({
      amount: cents, currency: "usd",
    });
    return { ok: r.status === "succeeded" };
  }
}

Your domain depends on Payee; swapping Stripe for a test fake is a constructor change at the composition root. Adapters also normalize errors, so callers see one failure type instead of a vendor's exception hierarchy.

Pitfalls and Interview Angles

Pitfalls and Interview Angles

Decorator order. Multiple decorators on one method apply bottom-up — the closest to the method runs first when wrapping. State the order explicitly when one decorator adds auth and another logs.

this loss. Passing obj.method as a callback detaches this; use obj.method.bind(obj) or an arrow property, or your wrapper's target.apply(this, args) sees undefined.

Adapter creep. Adapters should translate, not accumulate business logic; if validation and branching pile up, the domain model is leaking.

Interview: implement an adapter live, then explain standard versus legacy decorators and which tsconfig flag flips between them.

Quiz

1. Which tsconfig flag switches TypeScript to the legacy (experimental) decorator semantics?

Question 1 options

2. When two decorators stack on one method, in what order do they apply?

Question 2 options

3. What is the adapter pattern's primary job?

Question 3 options

Flashcards

Question

Standard vs legacy decorators

Answer

Standard = TS 5 default, (target, context) with context.name/kind; legacy needs experimentalDecorators.

Question

emitDecoratorMetadata

Answer

Legacy-only flag emitting type metadata DI frameworks read for constructor injection.

Question

Adapter

Answer

Object implementing your interface while delegating to an incompatible foreign one.

Question

Decorator evaluation order

Answer

Bottom-up: nearest decorator wraps first, outer decorators wrap the result.

Revision Notes

Key Takeaways

  • 1. Pick one decorator dialect per repo — standard or experimentalDecorators
  • 2. Adapters belong at the boundary: translate fields and errors, keep logic out
  • 3. Watch wrapping order and this-binding whenever you wrap methods

Interview Tips

  • • Write an adapter for a fake SDK on the spot, then mention which layer owns validation
  • • Explain decorator order bottom-up before the interviewer has to ask

Cheat Sheet

TS5 standard decorators by default; experimentalDecorators = legacy + metadata; decorators apply bottom-up; adapter wraps foreign API behind your interface.

Continue Learning