0
0
Typescriptprogramming~7 mins

Type-safe event emitter pattern in Typescript

Choose your learning style9 modes available
Introduction

We use a type-safe event emitter to make sure events and their data are correct. This helps avoid mistakes when sending or listening to events.

When building apps that need to send and listen to many different events.
When you want to catch mistakes early by checking event names and data types.
When multiple parts of your program need to talk to each other without being tightly connected.
When you want clear and safe communication between components or modules.
Syntax
Typescript
interface Events {
  eventName: EventDataType;
  anotherEvent: AnotherDataType;
}

class TypedEventEmitter<E> {
  on<K extends keyof E>(eventName: K, listener: (data: E[K]) => void): void {
    // ...implementation
  }
  emit<K extends keyof E>(eventName: K, data: E[K]): void {
    // ...implementation
  }
}

The Events interface defines event names and their data types.

The class uses generics to enforce correct event names and data when calling on and emit.

Examples
This example shows how to listen for a login event with user data, and then emit it.
Typescript
interface Events {
  login: { user: string };
  logout: void;
}

const emitter = new TypedEventEmitter<Events>();

emitter.on('login', data => {
  console.log(`User logged in: ${data.user}`);
});

emitter.emit('login', { user: 'Alice' });
Here, the logout event has no data, so we use void and pass undefined when emitting.
Typescript
emitter.on('logout', () => {
  console.log('User logged out');
});

emitter.emit('logout', undefined);
Sample Program

This program creates a type-safe event emitter with two events: message and error. It listens to both and prints messages when events happen.

Typescript
interface Events {
  message: { text: string };
  error: { code: number; message: string };
}

class TypedEventEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(data: E[K]) => void> } = {};

  on<K extends keyof E>(eventName: K, listener: (data: E[K]) => void): void {
    if (!this.listeners[eventName]) {
      this.listeners[eventName] = [];
    }
    this.listeners[eventName]!.push(listener);
  }

  emit<K extends keyof E>(eventName: K, data: E[K]): void {
    this.listeners[eventName]?.forEach(listener => listener(data));
  }
}

const emitter = new TypedEventEmitter<Events>();

emitter.on('message', data => {
  console.log(`Message received: ${data.text}`);
});

emitter.on('error', data => {
  console.log(`Error ${data.code}: ${data.message}`);
});

emitter.emit('message', { text: 'Hello, world!' });
emitter.emit('error', { code: 404, message: 'Not found' });
OutputSuccess
Important Notes

Type safety helps catch errors before running the program.

Always define your event names and data clearly in an interface.

This pattern works well in apps with many events and listeners.

Summary

Type-safe event emitters ensure events and data match expected types.

Use interfaces to list event names and their data types.

This pattern improves code safety and clarity when working with events.