Languages
[Edit]
EN

TypeScript - custom event listener class

12 points
Created by:
a_horse
538

In this article, we're going to have a look at how to write our own event listener class in TypeScript.

Custom event listener class concept implemented TypeScript.
Custom event listener class concept implemented 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 where actions are 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');

 

See also

  1. JavaScript - custom event listener class
Donate to Dirask
Our content is created by volunteers - like Wikipedia. If you think, the things we do are good, donate us. Thanks!
Join to our subscribers to be up to date with content, news and offers.
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join