HomeBlogPub Sub Typescript

Implement the publish-subscribe pattern in TypeScript

Published Jan 16, 2023
Updated Dec 15, 2024
1 minutes read

Recently I was working on a refactor of a personal project and I needed to implement a publish-subscribe pattern. I had never done it before, so I decided to do some research and write a simple implementation in TypeScript.

What is the publish-subscribe pattern?

The pub-sub pattern is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

Example

// subscribe.ts
 
export type ISubscribe<T> = {
  getState: () => T;
  setState: (value: Partial<T>) => void;
  subscribe: (callback: () => void) => () => void;
};
 
export const subscription = <T>(initialValue: T): ISubscribe<T> => {
  const listeners = new Set<() => void>();
  let value = initialValue;
 
  const getState = () => value;
 
  const setState = (newValue: Partial<T>) => {
    value = { ...value, ...newValue };
    listeners.forEach((listener) => listener());
  };
 
  const subscribe = (callback: () => void) => {
    listeners.add(callback);
    // Return a function to unsubscribe.
    return () => listeners.delete(callback);
  };
 
  return {
    getState,
    setState,
    subscribe,
  };
};

Testing the implementation

The test is using Vitest:

// subscribe.spec.ts
 
import { vi } from "vitest";
import { subscription } from ".";
 
describe("subscribe.ts", () => {
  const baseStore = {
    name: "foo",
    surname: "bar",
  };
 
  it("should test the subscription implementation", () => {
    const state = subscription(baseStore);
    const sub = vi.fn();
 
    expect(state.getState()).toEqual(baseStore);
 
    state.subscribe(sub);
 
    state.setState({ name: "baz" });
 
    expect(sub).toHaveBeenCalledTimes(1);
    expect(state.getState()).toEqual({ ...baseStore, name: "baz" });
  });
});