Decorators are a powerful way to enhance classes and their members (methods or properties) without touching their original code directly. 🎯
They let you decorate behaviour in a clean and reusable way. Like adding toppings to a pizza 🍕
⚠️ Decorators are a Stage-3 proposal in JavaScript. You can use them with TypeScript or Babel.
🔍 What is a Decorator?
A decorator is a special function you can attach to:
- A class
- A class method
- A class property
It looks like this:
@myDecorator
class MyClass {}
Or for methods:
class MyClass {
@log
doSomething() {}
}
⚙️ How Do Decorators Work Behind the Scenes?
Think of a decorator as a function that wraps the original function, property, or class to change its behavior.
Here's what happens behind the scenes for method decorators:
- When the class is defined, decorators are executed.
- The decorator function receives three arguments:
-
target
: The class prototype (for instance methods) or constructor (for static methods) -
propertyKey
: The name of the method/property being decorated -
descriptor
: The property descriptor that describes the method- The decorator can change the method (e.g., add logging) by modifying the descriptor.
Behind-the-scenes sketch:
function log(target, propertyKey, descriptor) {
// Modify the original function
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`Calling ${propertyKey} with`, args);
return originalMethod.apply(this, args);
};
return descriptor;
}
Decorators don’t execute at runtime like normal code — they execute when the class is first defined.
🧪 Basic Example – Logging Function Calls
Let’s see a working example:
function log(target, propertyName, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log(`📞 ${propertyName} called with`, args);
const result = original.apply(this, args);
console.log(`✅ ${propertyName} returned`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
calc.add(5, 3);
🧰 Real-World Use Cases
1. ✅ Logging and Debugging
Great for tracing code behavior during development.
2. 🔐 Authorization Guards
function requireAdmin(target, key, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
if (!this.isAdmin) {
throw new Error("⛔️ Access denied");
}
return original.apply(this, args);
};
return descriptor;
}
class Dashboard {
constructor(isAdmin) {
this.isAdmin = isAdmin;
}
@requireAdmin
deleteUser() {
console.log("🗑️ User deleted");
}
}
const admin = new Dashboard(true);
admin.deleteUser(); // 🗑️ User deleted
const guest = new Dashboard(false);
guest.deleteUser(); // ⛔️ Error: Access denied
3. 🧹 Auto-Binding Methods
function autobind(target, key, descriptor) {
const original = descriptor.value;
return {
configurable: true,
enumerable: false,
get() {
return original.bind(this);
},
};
}
class Printer {
message = "💨 Hello from Printer!";
@autobind
print() {
console.log(this.message);
}
}
const p = new Printer();
const printFn = p.print;
printFn(); // 💨 Hello from Printer!
🔬 Types of Decorators
JavaScript (and especially TypeScript) supports different types of decorators:
Type | Target | Purpose |
---|---|---|
Class decorator | Whole class | Modify or enhance a class |
Method decorator | Class method | Wrap or replace a method |
Property decorator | Class property | Add metadata or transformations |
Parameter decorator | Method param | Metadata for method parameters |
📊 Setup for TypeScript
Enable decorators in your tsconfig.json
:
{
"compilerOptions": {
"target": "ES6",
"experimentalDecorators": true
}
}
For Babel, use: @babel/plugin-proposal-decorators
🧠 Summary Table
Feature | Target | Purpose |
---|---|---|
@log |
Method | Logging/debugging |
@autobind |
Method | Fix this binding |
@requireAdmin |
Method | Authorization check |
💭 Deeper Insight: Why Decorators?
Decorators provide a declarative way to apply cross-cutting concerns like:
- ✅ Logging
- 🔁 Memoization
- 🔐 Access control
- 💾 Caching
- ⌚ Rate limiting
Rather than adding repetitive boilerplate in every method, you write it once in a decorator and reuse it across your codebase.
This makes your business logic clean, reusable, and easy to read 💡
💬 Final Thoughts
Decorators help keep your code clean, elegant, and modular.
They're like invisible helpers that wrap around your logic — magical, but under your control 💫
Use them wisely and your code will thank you!
📖 Resources
Happy Decorating! 🌟