Adding named events to your TypeScript classes (Part 4)

In a previous tutorial I explained how events can be implemented as properties on a class using Strongly Typed Events for TypeScript. In this tutorial I explore how you can give your classes named events using an IEventHandling interface or EventHandlingBase class.

Create an IEventHandling<TSender, TArgs>

First, let's inspect the interface.

/** Indicates the object is capable of handling named events. */
interface IEventHandling<TSender, TArgs> {

    subscribe(name: string, fn: (sender: TSender, args: TArgs) => void): void;

    unsubscribe(name: string, fn: (sender: TSender, args: TArgs) => void): void;
}

It looks like the IEvent interface, but it adds a name to the subscriptions.

Implement IntervalEventTester

I don't have a real life example for this feature. For now let's build an event generator that generates an event every x milliseconds.

class IntervalEventTester implements IEventHandling<IntervalEventTester, number>
{
    private _nr: number = 0;
    private _events: EventList<IntervalEventTester, number> = new EventList<IntervalEventTester, number>();

    constructor(interval: number) {
        setInterval(() => {
            let nr = this._nr += 1;
            this._events.get('ev-' + nr).dispatch(this, nr);
            this._events.remove('ev-' + nr);
        }, interval);
    }

    subscribe(name: string, fn: (sender: IntervalEventTester, args: number) => void): void {
        this._events.get(name).subscribe(fn);
    }

    unsubscribe(name: string, fn: (sender: IntervalEventTester, args: number) => void): void {
        this._events.get(name).unsubscribe(fn);
    }
}

I used an EventList to implement the IEventHandling interface as it stores events by name. We're basically exposing its features to the outside.

Using the events is pretty simple:

let x = new IntervalEventTester(100);

x.subscribe('ev-10', () => alert('EV-10 fired!'));
x.subscribe('ev-24', () => alert('EV-24 fired!'));

A disadvantage is this method is discoverability. The user can't look to the class signature to discover possible events.

Extending from EventHandlingBase<TSender, TArgs>

For convenience there is a base class that can be extended to do the same. It implements the ceremony and provides access to the events through a protected property.

/** Extends objects with event handling capabilities. */
abstract class EventHandlingBase<TSender, TArgs> implements IEventHandling<TSender, TArgs> {

    private _events: EventList<TSender, TArgs> = new EventList<TSender, TArgs>();

    protected get events(): EventList<TSender, TArgs> {
        return this._events;
    }

    subscribe(name: string, fn: (sender: TSender, args: TArgs) => void): void {
        this._events.get(name).subscribe(fn);
    }

    unsubscribe(name: string, fn: (sender: TSender, args: TArgs) => void): void {
        this._events.get(name).unsubscribe(fn);
    }
}

Note: this scenario is not applicable to all classes as polymorphy (multiple base classes) is not supported by TypeScript. This would be a great case for extension methods in TypeScript.

Our implementation with the base class makes things cleaner:

class IntervalEventTester2 extends EventHandlingBase<IntervalEventTester2, number>
{
    private _nr: number = 0;

    constructor(interval: number) {

        super();

        setInterval(() => {
            let nr = this._nr += 1;
            this.events.get('ev-' + nr).dispatch(this, nr);
            this.events.remove('ev-' + nr);
        }, interval);
    }
}

More

Check out the following:

  1. Isaac Borrero says:

    Thanks for this Series. It was helpful and I am going to use you design patterns…

    1. Kees C. Bakker says:

      Did you succeed in using it?

expand_less