TypeScript Cheatsheet
Decorators
Use this TypeScript reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Overview and Setup
Decorators are a Stage 3 TC39 proposal, fully supported in TypeScript 5.0+. Two syntaxes exist: the legacy decorator (TS < 5, experimentalDecorators: true) and the new Stage 3 decorator (TS 5+, no flag needed). Both are covered here.
// tsconfig.json for legacy decorators (TS < 5 or emitDecoratorMetadata) { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true // enables reflect-metadata support } }
// TS 5+ new decorators — no config flag required // Just use @decorator syntax — the compiler recognizes Stage 3 decorators by default
Class Decorators
New (Stage 3 / TS 5+)
// Receives: class constructor; can return a new class function sealed<T extends abstract new (...args: any[]) => any>(target: T) { Object.seal(target); Object.seal(target.prototype); return target; } @sealed class User { constructor(public name: string) {} } // With replacement class function singleton<T extends new (...args: any[]) => any>(Base: T) { let instance: InstanceType<T> | undefined; return class extends Base { constructor(...args: any[]) { super(...args); if (instance) return instance; instance = this as InstanceType<T>; } }; } @singleton class Database { connect() {} }
Legacy (experimentalDecorators)
// Legacy — receives constructor function function logger(ctor: Function) { console.log(`Class ${ctor.name} created`); } function addTimestamp<T extends { new(...args: any[]): {} }>(ctor: T) { return class extends ctor { createdAt = new Date(); }; } @logger @addTimestamp class Report { constructor(public title: string) {} }
Decorator Factories (Parametrized)
// A function that returns a decorator function prefix(text: string) { return function <T extends abstract new (...args: any[]) => any>(target: T) { return class extends target { constructor(...args: any[]) { super(...args); // @ts-ignore this.name = `${text} ${this.name}`; } }; }; } @prefix("Dr.") class Doctor { constructor(public name: string) {} } const d = new Doctor("Alice"); // d.name === "Dr. Alice" // Legacy factory function configurable(value: boolean) { return function(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; return descriptor; }; }
Method Decorators
New (Stage 3 / TS 5+)
// context.name — method name; context.addInitializer — run after class construction function log(target: Function, context: ClassMethodDecoratorContext) { const methodName = String(context.name); return function(this: any, ...args: any[]) { console.log(`Calling ${methodName} with`, args); const result = target.apply(this, args); console.log(`${methodName} returned`, result); return result; }; } class Calculator { @log add(a: number, b: number): number { return a + b; } }
Legacy Method Decorator
// Receives: prototype, method name, property descriptor function readonly(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.writable = false; return descriptor; } function deprecated(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.warn(`Method ${key} is deprecated`); return original.apply(this, args); }; return descriptor; } function memoize(target: any, key: string, descriptor: PropertyDescriptor) { const original = descriptor.value; const cache = new Map<string, any>(); descriptor.value = function(...args: any[]) { const cacheKey = JSON.stringify(args); if (!cache.has(cacheKey)) cache.set(cacheKey, original.apply(this, args)); return cache.get(cacheKey); }; return descriptor; } class MathService { @readonly PI = 3.14; @memoize fibonacci(n: number): number { if (n <= 1) return n; return this.fibonacci(n - 1) + this.fibonacci(n - 2); } @deprecated oldMethod() {} }
Property Decorators
New (Stage 3 / TS 5+)
// ClassFieldDecoratorContext — can add initializers function required(target: undefined, context: ClassFieldDecoratorContext) { context.addInitializer(function(this: any) { const key = context.name; Object.defineProperty(this, key, { get() { return this[`_${String(key)}`]; }, set(val) { if (val === undefined || val === null) throw new Error(`${String(key)} is required`); this[`_${String(key)}`] = val; }, }); }); } class Form { @required email!: string; }
Legacy Property Decorator
// Receives: prototype and property name (no descriptor) function validate(target: any, key: string) { let value: any; Object.defineProperty(target, key, { get: () => value, set: (val: any) => { if (typeof val !== "string") throw new TypeError(`${key} must be a string`); value = val; }, }); } class User { @validate name!: string; }
Accessor Decorators
New (Stage 3 / TS 5+)
// Decorates auto-accessors (accessor keyword — TS 4.9+) function clamp(min: number, max: number) { return function(target: ClassAccessorDecoratorTarget<unknown, number>, context: ClassAccessorDecoratorContext) { return { get(this: unknown) { return target.get.call(this); }, set(this: unknown, val: number) { target.set.call(this, Math.max(min, Math.min(max, val))); }, }; }; } class Slider { @clamp(0, 100) accessor value = 50; } const s = new Slider(); s.value = 150; // clamped to 100
Legacy Accessor Decorator
// Same signature as method decorator function enumerable(value: boolean) { return function(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.enumerable = value; return descriptor; }; } class Person { private _name = "Alice"; @enumerable(false) get name() { return this._name; } }
Parameter Decorators (Legacy Only)
// Receives: prototype, method name, parameter index function logParam(target: any, key: string, index: number) { const existing: number[] = Reflect.getOwnMetadata("log_params", target, key) ?? []; existing.push(index); Reflect.defineMetadata("log_params", existing, target, key); } class Api { greet(@logParam name: string, @logParam greeting: string): string { return `${greeting}, ${name}`; } }
Decorator Composition (Stacking)
// Decorators apply bottom-up (innermost first at runtime) @A @B @C class MyClass {} // Application order: C → B → A // (factories evaluated top-down: A() → B() → C(); decorators applied C → B → A) // Example function first() { return (t: any) => { console.log("first applied"); return t; }; } function second() { return (t: any) => { console.log("second applied"); return t; }; } @first() @second() class Test {} // Output: // "second applied" // "first applied"
Reflect Metadata (Legacy / NestJS / Angular)
// Requires: "emitDecoratorMetadata": true + npm install reflect-metadata import "reflect-metadata"; // Built-in metadata keys emitted by TypeScript Reflect.getMetadata("design:type", target, key); // property type Reflect.getMetadata("design:paramtypes", target, key); // constructor/method param types Reflect.getMetadata("design:returntype", target, key); // method return type // Custom metadata function Injectable(target: Function) { Reflect.defineMetadata("injectable", true, target); } function Inject(token: symbol) { return function(target: any, key: string | symbol, index: number) { Reflect.defineMetadata(`inject:${index}`, token, target); }; } @Injectable class Service { constructor(@Inject(Symbol("DB")) private db: Database) {} }
Common Decorator Patterns
// Bind method to instance (fixes `this` for callbacks) function bound(target: any, key: string, descriptor: PropertyDescriptor) { return { configurable: true, get(this: any) { const bound = descriptor.value.bind(this); Object.defineProperty(this, key, { value: bound, configurable: true }); return bound; }, }; } // Throttle a method function throttle(ms: number) { return function(_: any, __: string, descriptor: PropertyDescriptor) { const fn = descriptor.value; let lastCall = 0; descriptor.value = function(...args: any[]) { const now = Date.now(); if (now - lastCall >= ms) { lastCall = now; return fn.apply(this, args); } }; }; } // Time execution function time(target: any, key: string, descriptor: PropertyDescriptor) { const fn = descriptor.value; descriptor.value = function(...args: any[]) { const t0 = performance.now(); const result = fn.apply(this, args); console.log(`${key} took ${(performance.now() - t0).toFixed(2)}ms`); return result; }; } class Component { @bound handleClick() { console.log(this); } @throttle(300) onScroll() { /* ... */ } @time render() { /* expensive */ } }
TS 5+ Stage 3 Context Object Reference
| Decorator Type | Context Type | Key Properties |
|---|---|---|
| Class | ClassDecoratorContext | name, addInitializer |
| Method | ClassMethodDecoratorContext | name, static, private, addInitializer |
| Field | ClassFieldDecoratorContext | name, static, private, addInitializer |
| Accessor | ClassAccessorDecoratorContext | name, static, private, addInitializer |
| Getter | ClassGetterDecoratorContext | name, static, private, addInitializer |
| Setter | ClassSetterDecoratorContext | name, static, private, addInitializer |