Languages
[Edit]
EN

TypeScript - custom event listener class

9 points
Created by:
AnnLen
13390

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.

Below example provides the following API:

1. Single event listener:

// ------------------------------------------------------------------------
// One listener object per event.

var myListener = new SimpleListener();

myListener.add((a: string, b: string, c: string, n: string): void => { });
myListener.add((a: string, b: string, c: string, n: string): void => { });

myListener.fire('a', 'b', 'c', 'n');

2. Composed events listener

// ------------------------------------------------------------------------
// One listener object per multiple events - actions grouped by event name.

var myListener = new ComposedListener();

myListener.add('event-name-1', (a: string, b: string, c: string, n: string): void => { });
myListener.add('event-name-1', (a: string, b: string, c: string, n: string): void => { });
myListener.add('event-name-2', (a: string, b: string, c: string, n: string): void => { });

myListener.fire('event-name-1', 'a', 'b', 'c', 'n');
myListener.fire('event-name-2', 'a', 'b', 'c', 'n');

Note: go to this article to see JavaScript version.

 

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).

// ONLINE-RUNNER:browser;

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 => {
		if (action instanceof Function) {
			this.listeners.push(action);
            let removed = false;
            const remove = (): boolean => {
				if (removed) {
                  	return false;
                }
                removed = true;
                return this.remove(action);
			};
			return remove;
		}
		throw new Error( 'Indicated listener action is not function type.' );
	};

	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:


var myListener = new SimpleListener();

myListener.add((name: string, age: number, city: string): void => {
	console.log(`Function 1: ${name} ${age} ${city}`);
});
myListener.add((name: string, age: number, city: string): void => {
	console.log(`Function 2: ${name} ${age} ${city}`);
});
myListener.add((name: string, age: number, city: string): void => {
	console.log(`Function 3: ${name} ${age} ${city}`);
});

myListener.fire('John', 25, 'London');

2. Complex event listener class example

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:

// ONLINE-RUNNER:browser;

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 => {
		if (action instanceof Function) {
			this.listeners.push(action);
            let removed = false;
            const remove = (): boolean => {
				if (removed) {
                  	return false;
                }
                removed = true;
                return this.remove(action);
			};
			return remove;
		}
		throw new Error( 'Indicated listener action is not function type.' );
	};

	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 entity = this.listeners[event];
			if (entity) {
				return entity.getSize();
            }
			return 0;
		}
		return this.count;
	};

	public add = (event: string, action: ListenerAction): ListenerRemover => {
        const entity = this.listeners[event];
        let remove: ListenerRemover;
        if (entity == null) {
            const entity = new SimpleListener();
            remove = entity.add(action); // add method throws exception on incorrect action type
            this.listeners[event] = entity
        } else {
            remove = entity.add(action)
        }
        this.count += 1;
        const proxy = (): boolean => {
            if (remove()) {
                this.count -= 1;
                if (!entity.getSize()) {
                        delete this.listeners[event];
                }
                return true;
            }
            return false;
        };
        return proxy;
	};
	
    public remove = (event: string, action: ListenerAction): boolean => {
        const entity = this.listeners[event];
        if (entity == null) {
          	return false;
        }
      	if (action) {
            if (entity.remove(action)) {
                this.count -= 1;
                if (!entity.getSize()) {
                    delete this.listeners[event];
                }
              	return true;
            }
          	return false;
        } else {
        	this.count -= entity.getSize();
          	delete this.listeners[event];
          	entity.clean(); // cleaning because of add method result
          	return true;
        }
	};

	public fire = (event: string, parameters: unknown): void => {
		const entity = this.listeners[event];
		if (entity) {
			entity.fire(parameters);
        }
	};
  	
    public clean = (): void => {
      	if (this.count > 0) {
            const listeners = Object.values(this.listeners);
            for (let i = 0; i < listeners.length; ++i) {
                const listener = listeners[i];
                listener.clean(); // cleaning because of add method result
            }
            this.count = 0;
            this.listeners = { };
        }
    };
}


// Usage example:


var myListener = new ComposedListener();

myListener.add('event-name-1', (name: string, age: number, city: string): void => {
	console.log(`event-name-1 -> Function 1: ${name} ${age} ${city}`);
});
myListener.add('event-name-1', (name: string, age: number, city: string): void => {
	console.log(`event-name-1 -> Function 2: ${name} ${age} ${city}`);
});
myListener.add('event-name-2', (name: string, age: number, city: string): void => {
	console.log(`event-name-2 -> Function 3: ${name} ${age} ${city}`);
});

myListener.fire('event-name-1', 'John', 25, 'London');
myListener.fire('event-name-2', 'Kate', 21, 'Melbourne');
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