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 an event list can be used to support scenarios with classes with a multitude of events. There is a way to decrease the number of private backing variables.
The EventList
So let's inspect the small EventList class that will create and store named events using a map. When an event is requested by name, it will be automatically created and stored.
/** Storage class for multiple events that are accessible by name. * Events are automatically created. */ class EventList<TSender, TArgs> { private _events: { [name: string]: EventDispatcher<TSender, TArgs>; } = {}; get(name: string): EventDispatcher<TSender, TArgs> { let event = this._events[name]; if (event) { return event; } event = new EventDispatcher<TSender, TArgs>(); this._events[name] = event; return event; } remove(name: string): void { this._events[name] = null; } }
The Stopwatch
A stopwatch is an object that has multiple events in real live: start, pause, reset. I've modeled a simple stopwatch in a class and implemented the events using an EventList.
class Stopwatch { private _events: EventList<Stopwatch, StopwatchEventArgs> = new EventList<Stopwatch, StopwatchEventArgs>(); private _ticks: number = 0; private _timer: number; get onStart(): IEvent<Stopwatch, StopwatchEventArgs> { return this._events.get('onStart'); } get onPause(): IEvent<Stopwatch, StopwatchEventArgs> { return this._events.get('onPause'); } get onReset(): IEvent<Stopwatch, StopwatchEventArgs> { return this._events.get('onReset'); } private dispatch(name: string) { this._events.get(name).dispatch( this, new StopwatchEventArgs(this._ticks, this.display()) ); } start(): void { if (this._timer == null) { this._timer = Date.now(); this.dispatch('onStart'); } } pause(): void { if (this._timer) { this._ticks = this.getTicks(); this._timer = null; this.dispatch('onPause'); } } reset(): void { this._ticks = 0; this._timer = Date.now(); this.dispatch('onReset'); } getTicks(): number { if (this._timer) { return (Date.now() - this._timer) + this._ticks; } return this._ticks; } display(): string { var ticks = this.getTicks(); //get seconds from ticks var ts = ticks / 1000; //conversion based on seconds let hh: any = Math.floor(ts / 3600); let mm: any = Math.floor((ts % 3600) / 60); let ss: any = (ts % 3600) % 60; //prepend '0' when needed hh = hh < 10 ? '0' + hh : hh; mm = mm < 10 ? '0' + mm : mm; ss = ss < 10 ? '0' + ss : ss; //use it var str = hh + ":" + mm + ":" + ss; return str; } } class StopwatchEventArgs { private _ticks: number; private _display: string; get ticks(): number { return this._ticks; } get display(): string { return this._display; } constructor(ticks: number, display: string) { this._ticks = ticks; this._display = display; } }
To the outside, events are exposed as one expects:
var sw = new Stopwatch(); sw.onStart.subscribe((sender, args) => { alert('Stopwatch started after ' + args.display + '.'); }); sw.onPause.subscribe((sender, args) => { alert('Paused after ' + args.display + '.'); }); sw.start(); window.setTimeout(function () { sw.pause(); }, 3000); window.setTimeout(function () { sw.start(); }, 4000);
See it in action in JS Fiddle: https://jsfiddle.net/KeesCBakker/5qbj06rk/
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