Does TypeScript support events on classes?

Typescript

Typescript Problem Overview


I am just wondering if in TypeScript you can define custom events on your classes or interfaces?

What would this look like?

Typescript Solutions


Solution 1 - Typescript

How about this simplified event to be used as a property? Stronger typing of the owning class and no inheritance requirement:

interface ILiteEvent<T> {
    on(handler: { (data?: T): void }) : void;
    off(handler: { (data?: T): void }) : void;
}

class LiteEvent<T> implements ILiteEvent<T> {
    private handlers: { (data?: T): void; }[] = [];

    public on(handler: { (data?: T): void }) : void {
        this.handlers.push(handler);
    }

    public off(handler: { (data?: T): void }) : void {
        this.handlers = this.handlers.filter(h => h !== handler);
    }

    public trigger(data?: T) {
        this.handlers.slice(0).forEach(h => h(data));
	}

	public expose() : ILiteEvent<T> {
		return this;
	}
}

used like so:

class Security{
    private readonly onLogin = new LiteEvent<string>();
    private readonly onLogout = new LiteEvent<void>();

    public get LoggedIn() { return this.onLogin.expose(); } 
    public get LoggedOut() { return this.onLogout.expose(); }

    // ... onLogin.trigger('bob');
}

function Init() {
    var security = new Security();

    var loggedOut = () => { /* ... */ }

    security.LoggedIn.on((username?) => { /* ... */ });
	security.LoggedOut.on(loggedOut);

    // ...

    security.LoggedOut.off(loggedOut);
}

Improvements?

A gist for this

Solution 2 - Typescript

The NPM package Strongly Typed Events for TypeScript (GitHub) implements 3 types of events: IEvent<TSender, TArgs>, ISimpleEvent<TArgs> and ISignal. This makes it easier to use the right kind of event for your project. It also hides the dispatch method from your event, as good information hiding should do.

Event Types / Interfaces - The definitions of the events:

interface IEventHandler<TSender, TArgs> {
    (sender: TSender, args: TArgs): void
}

interface ISimpleEventHandler<TArgs> {
    (args: TArgs): void
}

interface ISignalHandler {
    (): void;
}

Example - This example shows how the 3 types of events can be implemented using a ticking clock:

class Clock {

    //implement events as private dispatchers:
    private _onTick = new SignalDispatcher();
    private _onSequenceTick = new SimpleEventDispatcher<number>();
    private _onClockTick = new EventDispatcher<Clock, number>();

    private _ticks: number = 0;

    constructor(public name: string, timeout: number) {
        window.setInterval( () => { 
            this.Tick(); 
        }, timeout);
    }

    private Tick(): void {
        this._ticks += 1;

        //trigger event by calling the dispatch method and provide data
        this._onTick.dispatch();
        this._onSequenceTick.dispatch(this._ticks);
        this._onClockTick.dispatch(this, this._ticks);
    }

    //expose the events through the interfaces - use the asEvent
    //method to prevent exposure of the dispatch method:
    public get onTick(): ISignal {
        return this._onTick.asEvent();
    }

    public get onSequenceTick() : ISimpleEvent<number>{
        return this._onSequenceTick.asEvent();
    }

    public get onClockTick(): IEvent<Clock, number> {
        return this._onClockTick.asEvent();
    }
}

Usage - It can be used like this:

let clock = new Clock('Smu', 1000);

//log the ticks to the console
clock.onTick.subscribe(()=> console.log('Tick!'));

//log the sequence parameter to the console
clock.onSequenceTick.subscribe((s) => console.log(`Sequence: ${s}`));

//log the name of the clock and the tick argument to the console
clock.onClockTick.subscribe((c, n) => console.log(`${c.name} ticked ${n} times.`))

Read more here: On events, dispatchers and lists (a general explanation of the system)

Tutorials
I've written a few tutorials on the subject:

Solution 3 - Typescript

I think you are asking if a class instance can implement addEventListener() and dispatchEvent() like a DOM element. If the class is not a DOM node, then you would have to write your own event bus. You would define an interface for a class that can publish events, then implement the interface in the your classes. Here is a naive example;

interface IEventDispatcher{
  // maintain a list of listeners
  addEventListener(theEvent:string, theHandler:any);

  // remove a listener
  removeEventListener(theEvent:string, theHandler:any);

  // remove all listeners
  removeAllListeners(theEvent:string);

  // dispatch event to all listeners
  dispatchAll(theEvent:string);

  // send event to a handler
  dispatchEvent(theEvent:string, theHandler:any);
}

class EventDispatcher implement IEventDispatcher {
  private _eventHandlers = {};

  // maintain a list of listeners
  public addEventListener(theEvent:string, theHandler:any) {
    this._eventHandlers[theEvent] = this._eventHandlers[theEvent] || [];
    this._eventHandlers[theEvent].push(theHandler);
  }

  // remove a listener
  removeEventListener(theEvent:string, theHandler:any) {
    // TODO
  }

  // remove all listeners
  removeAllListeners(theEvent:string) {
    // TODO
  }

  // dispatch event to all listeners
  dispatchAll(theEvent:string) {
    var theHandlers = this._eventHandlers[theEvent];
    if(theHandlers) {
      for(var i = 0; i < theHandlers.length; i += 1) {
        dispatchEvent(theEvent, theHandlers[i]);
      }
    }
  }

  // send event to a handler
  dispatchEvent(theEvent:string, theHandler:any) {
    theHandler(theEvent);
  }
}

Solution 4 - Typescript

You can use custom events in TypeScript. I'm not sure exactly what you are trying to do, but here is an example:

module Example {
    export class ClassWithEvents {
        public div: HTMLElement;

        constructor (id: string) {
            this.div = document.getElementById(id);

            // Create the event
            var evt = document.createEvent('Event');
            evt.initEvent('customevent', true, true);

            // Create a listener for the event
            var listener = function (e: Event) {
                var element = <HTMLElement> e.target;
                element.innerHTML = 'hello';
            }

            // Attach the listener to the event
            this.div.addEventListener('customevent', listener);

            // Trigger the event
            this.div.dispatchEvent(evt);
        }
    }
}

If you are looking to do something more specific please let me know.

Solution 5 - Typescript

If you are looking to get intelli-sense type checking using the standard emitter pattern you can now do the following:

type DataEventType = "data";
type ErrorEventType = "error";
declare interface IDataStore<TResponse> extends Emitter {
	on(name: DataEventType, handler : (data: TResponse) => void);	
    on(name: ErrorEventType, handler: (error: any) => void);	
}

Solution 6 - Typescript

This solution allows you to directly write the parameters in the function call instead of needing to wrap all your parameters in an object.

interface ISubscription {
   (...args: any[]): void;
}

class PubSub<T extends ISubscription> {
    protected _subscribed : ISubscriptionItem[] = [];

    protected findSubscription(event : T) : ISubscriptionItem {
        this._subscribed.forEach( (item : ISubscriptionItem) =>{
            if (item.func==event)
              return item;
        } );
        return null;
    }

    public sub(applyObject : any,event : T) {
        var newItem = this.findSubscription(event);
        if (!newItem) {
            newItem = {object : applyObject, func : event };
            this._subscribed.push(newItem);
            this.doChangedEvent();
        }
    }
    public unsub(event : T) {
        for ( var i=this._subscribed.length-1 ; i>=0; i--) {
            if (this._subscribed[i].func==event)
                this._subscribed.splice(i,1);
        }
        this.doChangedEvent();
    }
    protected doPub(...args: any[]) {
        this._subscribed.forEach((item : ISubscriptionItem)=> {
            item.func.apply(item.object, args);
        })
    }

    public get pub() : T {
        var pubsub=this;
        var func=  (...args: any[]) => {
            pubsub.doPub(args);
        }
        return <T>func;
    }

    public get pubAsync() : T {
        var pubsub=this;
        var func =  (...args: any[]) => {
            setTimeout( () => {
                pubsub.doPub(args);
            });
        }
        return <T>func;
    }

    public get count() : number {
        return this._subscribed.length
    }

}

Usage:

interface ITestEvent {
	(test : string): void;
}

var onTestEvent = new PubSub<ITestEvent>();
//subscribe to the event
onTestEvent.sub(monitor,(test : string) => {alert("called:"+test)});
//call the event
onTestEvent.pub("test1");

Solution 7 - Typescript

Here's a simple example of adding custom-type events to your class, using sub-events:

class MyClass {

    readonly onMessage: SubEvent<string> = new SubEvent();
    readonly onData: SubEvent<MyCustomType> = new SubEvent();

    sendMessage(msg: string) {
        this.onMessage.emit(msg);
    }

    sendData(data: MyCustomType) {
        this.onData.emit(data);
    }
}

And then any client can subscribe to receive those events:

const a = new MyClass();

const sub1 = a.onMessage.subscribe(msg => {
    // msg here is strongly-typed
});

const sub2 = a.onData.subscribe(data => {
    // data here is strongly-typed
});

And when you no longer need the events, you can cancel the subscriptions:

sub1.cancel();

sub2.cancel();

Solution 8 - Typescript

You can use rxjs to achieve this.

Declare following in your class:

export class MyClass {
    private _eventSubject = new Subject();
   
    public events = this._eventSubject.asObservable();

    public dispatchEvent(data: any) {
        this._eventSubject.next(data);
    }
}

And then you can trigger the event this way:

let myClassInstance = new MyClass();
myClassInstance.dispatchEvent(data);

And listen to this event in a following way:

myClassInstance.events.subscribe((data: any) => { yourCallback(); });

Solution 9 - Typescript

You can find an event dispatcher declaration at YouTube. Following the video you will be able to have a fully typed version of the event dispatcher

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCompareTheMooCatView Question on Stackoverflow
Solution 1 - TypescriptJason KlebanView Answer on Stackoverflow
Solution 2 - TypescriptKees C. BakkerView Answer on Stackoverflow
Solution 3 - TypescriptEzwardView Answer on Stackoverflow
Solution 4 - TypescriptFentonView Answer on Stackoverflow
Solution 5 - TypescriptJason YoungView Answer on Stackoverflow
Solution 6 - Typescriptuser2866593View Answer on Stackoverflow
Solution 7 - Typescriptvitaly-tView Answer on Stackoverflow
Solution 8 - Typescriptmichal.jakubeczyView Answer on Stackoverflow
Solution 9 - TypescriptJailenView Answer on Stackoverflow