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:
- Strongly typed event handlers in TypeScript (Part 1)
- Using strongly typed events in TypeScript with interfaces (Part 2)
- Strongly Typed Events in TypeScript using an event list (Part 3)
- Adding named events to your classes (Part 4)
- GitHub: https://github.com/KeesCBakker/Strongly-Typed-Events-for-TypeScript
Thanks for this Series. It was helpful and I am going to use you design patterns…
Did you succeed in using it?