Tomáš Wróbel

Native Event Emitter

Today, let’s see how to create TypeScript Event Emitter utility. We will use standard EventTarget constructor.

EventTarget itself

All DOM elements inherits from EventTarget. Unlike them, EventTarget is a legal constructor.

See:

const target = new EventTarget();

target.addEventListener("some_event", function (e: Event) {
    console.log(e);
});

To emit an event we need construct Event:

target.dispatchEvent(new Event("some_event"));

Extending Event Target

We will extend EventTarget to mimic EventEmitter

EventTarget EventEmitter
addEventListener on, once
removeEventListener off
dispatchEvent emit
class EventEmitter extends EventTarget {
    on(type: string, func: () => void) {
        this.addEventListener(type, func);
    }

    once(type: string, func: () => void) {
        this.addEventListener(type, func, {once: true});
    }

    off(type: string, func: () => void) {
        this.removeEventListener(type, func);
    }

    emit(type: string) {
        this.dispatchEvent(new Event(type));
    }
}

Extending Event

What about handling some data?

class SomeEvent extends Event {
    // Some data
    priority = 3;

    constructor() {
        super("some_event");
    }
}

target.addEventListener("some_event", function (e: SomeEvent) {
    console.log(e.priority);
});

target.dispatchEvent(new SomeEvent());

Typings

class Emitter<E extends Record<string, new (...args: any[]) => Event>> extends EventTarget {
    constructor(public events: E) {
        super();
    }

    on<T extends keyof E>(event: T, listener: (e: InstanceType<E[T]>) => void) {
        this.addEventListener(event as string, listener as (e: Event) => void);
        return this;
    }

    once<T extends keyof E>(event: T, listener: (e: InstanceType<E[T]>) => void) {
        this.addEventListener(event as string, listener as (e: Event) => void, {once: true});
        return this;
    }

    off<T extends keyof E>(event: T, listener: (e: InstanceType<E[T]>) => void) {
        this.removeEventListener(event as string, listener as (e: Event) => void);
        return this;
    }

    emit<T extends keyof E>(event: T, ...args: ConstructorParameters<E[T]>) {
        this.dispatchEvent(new this.events[event](...args));
        return this;
    }
}

We can use the Emitter with predefined constructors:

class AdvancedEvent extends Event {
    constructor(public x: number, public y: number) {
        super("advanced");
    }
}

const emitter = new Emitter({
    base: Event,
    advanced: AdvancedEvent,
});

emitter.on("advanced", function ({x, y}) {
    console.log(x, y);
});

emitter.emit("base", "advanced");
emitter.emit("advanced", 7, 8);
This project is archived. New website: tomaswrobel.dev