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?
2. When two decorators stack on one method, in what order do they apply?
3. What is the adapter pattern's primary job?
Flashcards
Question
Standard vs legacy decorators
Click to reveal answer
Answer
Standard = TS 5 default, (target, context) with context.name/kind; legacy needs experimentalDecorators.
Question
emitDecoratorMetadata
Click to reveal answer
Answer
Legacy-only flag emitting type metadata DI frameworks read for constructor injection.
Question
Adapter
Click to reveal answer
Answer
Object implementing your interface while delegating to an incompatible foreign one.
Question
Decorator evaluation order
Click to reveal answer
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.