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