Design Patterns in JavaScript -Part 3: The Observer Pattern
“React to changes. Decouple logic. Keep your code alive.”
Imagine this:
- A button is clicked… and five other components need to react.
- Or a cart is updated, and multiple parts of the UI need to re-render.
- Or a websocket receives new data, and you want to broadcast it cleanly.
This is exactly what the Observer Pattern is made for.
Let’s break it down — clearly, practically, and with real JavaScript examples.
🧠 What is the Observer Pattern?
It’s a behavioral design pattern where:
- One object (subject) maintains a list of dependents (observers)
- Whenever something changes, it notifies all observers
Sound familiar? That’s how EventEmitter, Redux, and even useEffect() work under the hood.
📦 Use Case 1: Custom Event Emitter in Vanilla JS
Let’s build a simple custom emitter:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, fn) {
(this.events[event] ||= []).push(fn);
}
emit(event, data) {
(this.events[event] || []).forEach(fn => fn(data));
}
off(event, fnToRemove) {
this.events[event] = (this.events[event] || []).filter(fn => fn !== fnToRemove);
}
}
Usage:
const emitter = new EventEmitter();
function onUserLogin(data) {
console.log('User logged in:', data);
}
emitter.on('login', onUserLogin);
emitter.emit('login', { name: 'Sachin' }); // logs: User logged in: { name: 'Sachin' }
✅ Decoupled logic
✅ Reusable in any JS app
✅ Mimics Node.js EventEmitter or window.addEventListener
⚛️ Use Case 2: React State Subscriptions
You can implement a mini Redux-style observer pattern:
const listeners = [];
export const store = {
state: { count: 0 },
subscribe(fn) {
listeners.push(fn);
},
setState(newState) {
this.state = { ...this.state, ...newState };
listeners.forEach(fn => fn(this.state));
},
};
React component:
useEffect(() => {
store.subscribe(setLocalState);
}, []);
🧠 Now any part of your app can react to global changes without tight coupling.
📡 Use Case 3: Broadcasting WebSocket Data
Imagine a stock ticker app:
ws.onmessage = (msg) => {
observers.forEach((fn) => fn(msg.data));
};
function subscribeToStock(fn) {
observers.push(fn);
}
Now your price chart, recent trades table, and alert component can independently subscribe.
🧪 Real Project Examples
🧠 TL;DR
🔜 Coming Up Next: Strategy Pattern
In Part 4, we’ll look at how to create pluggable logic using the Strategy Pattern — useful for:
- Payment flow
- Sorting & filtering
- Validation systems
- Rule engines
👉 Follow on Medium to get the next drop — clean code, real use cases, no fluff.
Top comments (0)