event-property
v1.2.3
Published
Alternative approach to implementing events concept.
Downloads
3
Maintainers
Readme
event-property
An alternative approach to implementing events. TypeScript-Way.
This library provides an EventProperty
class which works
similar to well-known EventEmitter
from NodeJS 'events'
module and DOM events and some others. The different approach
is to create a separate managing object for each event type instead
of having a single "event manager" object driven by string arguments.
Installation
npm install --save event-property
Usage
import EventProperty from 'EventProperty';
let event = new EventProperty<string>();
event.on((arg: string) => {
console.log(arg);
});
event.emit('Hi!');
Purpose
Common EventEmitter
approach undermines benefits of strong typing
because it uses string literals for event identification. This means that
misspelling event name cannot be identified as an error neither during
source parsing nor in runtime during the execution. This also means
poor or no autocompletion and intelligence for ide. Look at this example:
let x = new EventEmitter();
x.on("render", () => {
console.log("rendering stuff");
});
x.trigger("rendr"); // See the mistake here?
It's pretty difficult to spot this mistake especially if your code is more
than 4 lines. What's more troublesome is that this code compiles and runs
fine except nothing is happening. That is because to common EventEmitter
"rendr"
is actually just another valid event. Also when you dive into
someone else's code if it not documented very well(which is the case most
of the time) you have no clue which events are flying around there until
you carefully read the whole code. Of course you may define constants for
event names, but you have to keep them in some different place and
export and import them and it's still convention. You have to remember
and follow conventions wasting you mind on this and often other people
don't want to. Also if you inherit from EventEmitter
some minor issues
may arise like inabilty to limit access to triggering events from
outside of the class and requirement to put EventEmitter at the root
of inheritance tree and some others.
The alternative approach is presenting each event as a separate object
having its own on()
and emit()
methods.
This concept is sometimes referred to as signals and slots and certainly
there are other libraries implementing it like ts-events.
I've written this lib for several reasons. I needed a certain set of features and some features of other libraries where unnecessary and excessive for my tasks. I wanted to have deep understanding of how my events work and ability to change details of that behaviour easily on the spot. So I don't claim that this library is best of it's kind or anything like that.
Features
This library implements several additional features I deemed useful for my purposes
once
: listen to a single next occurrence of event, actually pretty common feature for any events.next
:once
feature in form of a promise.init
: adds a special handler which is invoked after the event was triggered for the first time. Argument from the first emit is passed to handler. Useful for initialization events because you don't have to care to put a listener on it before the event occurs - if the event was already emitted than provided callback will be invoked right away.match
: attach a listener which is invoked only if the events argument meets some condition. Usefull for stuff like keyboard events (listening to a certain key being pressed for example).pipe
: redirect all event occurrences to anotherEventProperty
. Useful when you needed to simple "buble up" some events from a child to the parent of the complex model system for example.EventProperty.Emitter
: an interface ofEventProperty
which hides theemit
method. Useful for describing public interfaces to events.- bound
emit
method - theemit
method is bound toEventProperty
by design thus you may pass it anywhere as callback without any additional manipulations.
API
interface EventProperty<T> {
on(handler: EventProperty.Handler<T>, context?: Object): ListenerId;
once(handler: EventProperty.Handler<T>, context?: Object): ListenerId;
match(value: T|RegExp, handler: EventProperty.Handler<T>, context?: Object): ListenerId;
matchOnce(value: T|RegExp, handler: EventProperty.Handler<T>, context?: Object): ListenerId;
pipe(destination: EventProperty.Emitter<T>): ListenerId;
route(value: T|RegExp, destination: EventProperty.Emitter<T>): ListenerId;
init(handler: EventProperty.Handler<T>, context?: Object): void;
off(handler: EventProperty.Handler<T>): void;
off(handler: EventProperty.Handler<T>, context: Object): void;
off(context: Object): void;
off(id: ListenerId): void;
off(destination: EventProperty.Emitter<T>): void;
off(): void;
emit(eventArgument: T): void;
/*static*/ make<T>(): [EventProperty<T>, EventProperty.Emitter<T>];
/*static*/ split<T>(): [EventProperty.EmitMethod<T>, EventProperty.Emitter<T>];
/*static*/ splitVoid(): [EventProperty.VoidEmitMethod, EventProperty.Emitter<void>];
}
interface EventProperty.Void extends EventProperty<void> {
emit(): void;
}
Examples
Look for more examples in tests
Limiting access to emit
in a class
import EventProperty from 'event-property';
class MyClass {
// An event
private readonly _somethingHappened: EventProperty<number> = new EventProperty<number>();
public get somethingHappened(): EventProperty.Emitter<number> { return this._somethingHappened; }
// Method triggering that event
doSomething() {
this._somethingHappened.emit(1);
}
}
let myInstance = new MyClass();
myInstance.somethingHappened.on((arg: number) => {
console.log('happened:', arg); // happened: 1
});
Using initialization event
import EventProperty from 'event-property';
let initEvent = new EventProperty.Void();
setTimeout(() => {
initEvent.emit();
}, 5000);
setTimeout(() => {
// We don't have to worry about missing or not an already emitted 'init' event.
initEvent.init(() => {
console.log('Doing stuff reliably after initialization');
});
}, Math.floor(Math.random() * 10000));
Using match
to handle a certain key
import EventProperty from 'event-property';
let keyPressed = new EventProperty();
window.onkeydown = keyPressed.emit;
keyPressed.match({keyCode: 27}, () => console.log('Escape pressed'));