TYPESCRIPT
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:
{
"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
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.
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:
- Log class instantiations, method calls, or property changes.
- Restrict access to certain methods based on user roles.
- 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:
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:
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:
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:
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:
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:
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:
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:
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:
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.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.