Course

TypeScript Decorators: Syntax, Usage, and Examples

TypeScript decorators modify classes, methods, properties, or parameters at runtime. They add metadata and enhance functionality without changing the original code structure. Decorators follow the decorator pattern in TypeScript and enable powerful abstractions in object-oriented programming.

How to Use TypeScript Decorators

To use decorators, enable experimental support in your tsconfig.json file:

json
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true} }

A decorator is a function that starts with @ and applies to a class, method, property, or parameter.

Basic Class Decorator

tsx
function Logger(constructor: Function) { console.log(`Class ${constructor.name} was created.`); } @Logger class User { constructor(public name: string) {} }

When you create a new User, the decorator logs the class creation.

tsx
const user = new User("Alice"); // Output: Class User was created.

When to Use TypeScript Decorators

Decorators provide a clean way to extend functionality. You can use them to:

  1. Log class instantiations, method calls, or property changes.
  2. Restrict access to certain methods based on user roles.
  3. Cache results with a cache decorator for better performance.

Examples of TypeScript Decorators

Class Decorator

A class decorator modifies an entire class. The example below adds a timestamp property to a class:

tsx
function Timestamp<T extends { new (...args: any[]): {} }>(constructor: T) { return class extends constructor { timestamp = new Date(); }; } @Timestamp class Message { constructor(public content: string) {} } const msg = new Message("Hello"); console.log(msg.timestamp); // Outputs current timestamp

Method Decorator

A method decorator modifies a method’s behavior. The example below logs every method call:

tsx
function Log(target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${methodName} with arguments:`, args); return originalMethod.apply(this, args); }; } class Calculator { @Log add(a: number, b: number): number { return a + b; } } const calc = new Calculator(); calc.add(3, 7); // Output: Calling add with arguments: [3, 7]

Property Decorator

A property decorator modifies properties. The example below ensures a property always stores a capitalized string:

tsx
function Capitalize(target: any, propertyKey: string) { let value: string; const getter = function () { return value; }; const setter = function (newVal: string) { value = newVal.charAt(0).toUpperCase() + newVal.slice(1); }; Object.defineProperty(target, propertyKey, { get: getter, set: setter }); } class Person { @Capitalize name: string = ""; constructor(name: string) { this.name = name; } } const p = new Person("john"); console.log(p.name); // Output: John

Parameter Decorator

A parameter decorator modifies function parameters. The example below logs method arguments:

tsx
function LogParam(target: any, methodName: string, paramIndex: number) { console.log(`Parameter at index ${paramIndex} in method ${methodName} is logged.`); } class UserService { createUser(@LogParam name: string) { console.log(`User ${name} created.`); } } const service = new UserService(); service.createUser("Alice"); // Output: Parameter at index 0 in method createUser is logged.

Learn More About TypeScript Decorators

How to Create a Decorator

A decorator is a function that takes parameters based on where you apply it:

  • Class decorators receive the class constructor.
  • Method decorators receive the target, method name, and descriptor.
  • Property decorators receive the target and property key.
  • Parameter decorators receive the target, method name, and parameter index.

This decorator factory accepts arguments:

tsx
function Prefix(prefix: string) { return function (target: any, propertyKey: string) { let value: string; const getter = () => value; const setter = (newValue: string) => { value = `${prefix} ${newValue}`; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter }); }; } class Book { @Prefix("Title:") name: string = ""; constructor(name: string) { this.name = name; } } const b = new Book("TypeScript Guide"); console.log(b.name); // Output: Title: TypeScript Guide

Using Decorators Without a Class

Decorators usually work with classes, but you can apply them to standalone functions using higher-order functions:

tsx
function MeasureTime(fn: Function) { return function (...args: any[]) { console.time("Execution time"); const result = fn(...args); console.timeEnd("Execution time"); return result; }; } const slowFunction = MeasureTime(() => { for (let i = 0; i < 1e6; i++) {} }); slowFunction(); // Outputs execution time

Async Decorator

An async decorator handles asynchronous function calls. The example below retries an async operation on failure:

tsx
function Retry(retries: number) { return function (target: any, methodName: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { let attempts = 0; while (attempts < retries) { try { return await originalMethod.apply(this, args); } catch (error) { attempts++; console.log(`Retry ${attempts} for ${methodName}`); } } throw new Error(`${methodName} failed after ${retries} attempts`); }; }; } class API { @Retry(3) async fetchData() { throw new Error("Network error"); } } const api = new API(); api.fetchData().catch(console.error);

Cache Decorator

A cache decorator stores function results to improve performance:

tsx
function Cache(target: any, methodName: string, descriptor: PropertyDescriptor) { const cache = new Map(); const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const key = JSON.stringify(args); if (!cache.has(key)) { cache.set(key, originalMethod.apply(this, args)); } return cache.get(key); }; } class MathOperations { @Cache factorial(n: number): number { return n <= 1 ? 1 : n * this.factorial(n - 1); } } const math = new MathOperations(); console.log(math.factorial(5)); // Cached result used for efficiency

Fastify Decorators

Fastify, a web framework, supports decorators to extend server functionality:

tsx
import Fastify from "fastify"; const fastify = Fastify(); fastify.decorate("greet", (name: string) => `Hello, ${name}!`); fastify.get("/greet/:name", (request, reply) => { reply.send({ message: fastify.greet(request.params.name) }); }); fastify.listen(3000, () => console.log("Server running..."));

TypeScript decorators let you enhance class-based programming. You can use them for logging, validation, caching, and async operations without modifying core logic.

Looking to dive deeper into decorators and other essential TypeScript concepts? Check out our TypeScript course.