Event Bus and Observer Pattern in TypeScript: Implementation and Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event Bus and Observer Pattern in TypeScript: From Theory to Practice



Modern web development requires a flexible and scalable architecture. Two key patterns that help organize interaction between components are Event Bus and Observer. In this article, we will break down their implementation in TypeScript, advantages, and practical use cases.



What is the Observer Pattern?



The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When one object (the publisher) changes its state, all dependent objects (subscribers) are automatically notified and updated.



Main elements of the pattern:

  • Subject (Publisher) — the object that tracks changes and notifies subscribers.
  • Observer (Subscriber) — the object that subscribes to changes from the publisher.
  • Event Bus — a centralized mechanism that manages subscriptions and dispatches events between components.


Implementing Event Bus in TypeScript



TypeScript provides excellent capabilities for strict typing, making the Event Bus implementation safe and predictable. Let's look at a basic implementation:



type EventHandler<T = any> = (payload: T) => void;

class EventBus { private listeners: Map<string, EventHandler[]> = new Map();

// Subscribe to an event subscribe<T>(event: string, handler: EventHandler<T>): void { if (!this.listeners.has(event)) { this.listeners.set(event, []); } this.listeners.get(event)!.push(handler); }

// Unsubscribe from an event unsubscribe<T>(event: string, handler: EventHandler<T>): void { const handlers = this.listeners.get(event); if (handlers) { this.listeners.set( event, handlers.filter(h => h !== handler) ); } }

// Publish an event emit<T>(event: string, payload: T): void { const handlers = this.listeners.get(event); if (handlers) { handlers.forEach(handler => handler(payload)); } }

// Clear all subscriptions clear(): void { this.listeners.clear(); }}

export const eventBus = new EventBus();


Usage Example with Typed Events



To improve type safety, you can define event interfaces:



// Define event typesinterface AppEvents {  'user:login': { userId: string; timestamp: number };  'user:logout': { userId: string };  'data:update': { id: string; data: unknown };  'notification:show': { message: string; type: 'success' | 'error' | 'info' };}

// Create a typed busclass TypedEventBus { private listeners: Map<string, Function[]> = new Map();

on<K extends keyof AppEvents>( event: K, handler: (payload: AppEvents[K]) => void ): void { if (!this.listeners.has(event)) { this.listeners.set(event, []); } this.listeners.get(event)!.push(handler); }

emit<K extends keyof AppEvents>( event: K, payload: AppEvents[K] ): void { const handlers = this.listeners.get(event); if (handlers) { handlers.forEach(handler => handler(payload)); } }

off<K extends keyof AppEvents>( event: K, handler: (payload: AppEvents[K]) => void ): void { const handlers = this.listeners.get(event); if (handlers) { this.listeners.set( event, handlers.filter(h => h !== handler) ); } }}

export const typedBus = new TypedEventBus();


Practical Example: Notification System



Let's consider a real-world scenario — creating a notification system using Event Bus:

Recommendations