TypeScript - custom event listener class
In this article, we're going to have a look at how to write our own event listener class in TypeScript.
The motivation to write own class to manage events is lack of support in some cases.
The below example provides the following API:
1. Single event listener:
// ------------------------------------------------------------------------
// One listener object per event.
const listener = new SimpleListener();
listener.add((a: string, b: string, c: string, n: string): void => { });
listener.add((a: string, b: string, c: string, n: string): void => { });
listener.fire('a', 'b', 'c', 'n');
2. Composed events listener
// ------------------------------------------------------------------------
// One listener object per multiple events - actions grouped by event name.
const listener = new ComposedListener();
listener.add('event-name-1', (a: string, b: string, c: string, n: string): void => { });
listener.add('event-name-1', (a: string, b: string, c: string, n: string): void => { });
listener.add('event-name-2', (a: string, b: string, c: string, n: string): void => { });
listener.fire('event-name-1', 'a', 'b', 'c', 'n');
listener.fire('event-name-2', 'a', 'b', 'c', 'n');
Note: go to this article to see JavaScript version.
The presented solutions are split into two separated examples:
- simple event listener - this collection allows to keep many callback functions together and call them together,
- complex event listener this collection is similar to the first one with a major difference, the collection groups callback functions into separated events by given event name (
event-name-1
,event-name-2
, etc.).
1. Simple event listener class example
Presented collection in this section allows to: add, remove, fire, and clean events. add
method returns function reference that removes added event (just by calling it without any arguments).
type ListenerRemover = () => boolean;
type ListenerAction = (...args: Array<any>) => void;
// This class let's to add functions that are fire method is called.
//
class SimpleListener {
private listeners: Array<ListenerAction> = [ ];
public getSize = (): number => {
return this.listeners.length;
};
public add = (action: ListenerAction): ListenerRemover => {
this.listeners.push(action);
let removed = false;
return (): boolean => {
if (removed) {
return false;
}
removed = true;
return this.remove(action);
};
};
public remove = (action: ListenerAction): boolean => {
for (let i = 0; i < this.listeners.length; ++i) {
if (action === this.listeners[i]) {
this.listeners.splice(i, 1);
return true;
}
}
return false;
};
public fire = (...args: Array<any>): void => {
for (let i = 0; i < this.listeners.length; ++i) {
const listener = this.listeners[i];
listener.apply(listener, args);
}
};
public clean = (): void => {
this.listeners = [ ];
};
}
// Usage example:
const listener = new SimpleListener();
listener.add((name: string, age: number, city: string): void => {
console.log(`Function 1: ${name} ${age} ${city}`);
});
listener.add((name: string, age: number, city: string): void => {
console.log(`Function 2: ${name} ${age} ${city}`);
});
listener.add((name: string, age: number, city: string): void => {
console.log(`Function 3: ${name} ${age} ${city}`);
});
listener.fire('John', 25, 'London');
2. Complex event listener class example
The presented collection in this section provides the same operations as in the first example: add, remove, fire, and clean. The main difference is events grouping - the methods take as the first argument name of the event. Check the below example to know how it works:
type ListenerRemover = () => boolean;
type ListenerAction = (...args: Array<any>) => void;
// This class let's to add functions that are fire method is called.
//
class SimpleListener {
private listeners: Array<ListenerAction> = [ ];
public getSize = (): number => {
return this.listeners.length;
};
public add = (action: ListenerAction): ListenerRemover => {
this.listeners.push(action);
let removed = false;
return (): boolean => {
if (removed) {
return false;
}
removed = true;
return this.remove(action);
};
};
public remove = (action: ListenerAction): boolean => {
for (let i = 0; i < this.listeners.length; ++i) {
if (action === this.listeners[i]) {
this.listeners.splice(i, 1);
return true;
}
}
return false;
};
public fire = (...args: Array<any>): void => {
for (let i = 0; i < this.listeners.length; ++i) {
const listener = this.listeners[i];
listener.apply(listener, args);
}
};
public clean = (): void => {
this.listeners = [ ];
};
}
// This class let's to use named events.
//
class ComposedListener {
private count: number = 0;
private listeners: Record<string, SimpleListener> = { };
public getSize = (event?: string | null): number => {
if (event) {
const entry = this.listeners[event];
if (entry) {
return entry.getSize();
}
return 0;
}
return this.count;
};
public add = (event: string, action: ListenerAction): ListenerRemover => {
this.count += 1;
const entry = this.listeners[event] ?? (this.listeners[event] = new SimpleListener());
const remove = entry.add(action)
return (): boolean => {
if (remove()) {
this.count -= 1;
if (!entry.getSize()) {
delete this.listeners[event];
}
return true;
}
return false;
};
};
public remove = (event: string, action: ListenerAction): boolean => {
const entry = this.listeners[event];
if (entry == null) {
return false;
}
if (action) {
if (entry.remove(action)) {
this.count -= 1;
if (!entry.getSize()) {
delete this.listeners[event];
}
return true;
}
return false;
} else {
this.count -= entry.getSize();
delete this.listeners[event];
return true;
}
};
public fire = (event: string, parameters: unknown): void => {
const entry = this.listeners[event];
if (entry) {
entry.fire(parameters);
}
};
public clean = (): void => {
if (this.count > 0) {
this.count = 0;
this.listeners = { };
}
};
}
// Usage example:
const listener = new ComposedListener();
listener.add('event-name-1', (name: string, age: number, city: string): void => {
console.log(`event-name-1 -> Function 1: ${name} ${age} ${city}`);
});
listener.add('event-name-1', (name: string, age: number, city: string): void => {
console.log(`event-name-1 -> Function 2: ${name} ${age} ${city}`);
});
listener.add('event-name-2', (name: string, age: number, city: string): void => {
console.log(`event-name-2 -> Function 3: ${name} ${age} ${city}`);
});
listener.fire('event-name-1', 'John', 25, 'London');
listener.fire('event-name-2', 'Kate', 21, 'Melbourne');