5

I am trying to figure out how to create a custom event for a class in TypeScript. Examples like this one didn't help me much in understanding how to do that.

My example class looks like the following.

Cat.ts:

export class Cat {
    public getName(): string {
        return this.catName;
    }

    public setName(catName: string) {
        this.catName = catName;
    }

    constructor(private catName: string) { }

    public makeMeow() {
        this.onWillMeow();
        console.log("Cat meows!");
        this.onDidMeow();
    }

    public onWillMeow() {
        console.log("onWillMeow");
    }

    public onDidMeow() {
        console.log("onDidMeow");
    }
}

Now I would like to be able to declare events from outside like the following code aims to demonstrate.

const myCat: Cat = new Cat("Tikki");
myCat.onWillMeow({event => {
     console.log("Tikki the cat is just about to meow!");
}});
myCat.onWillMeow({event => {
     console.log("Tikki the cat did just meow!");
}});
myCat.makeMeow();

Now, I would imagine to get some output like this one:

onWillMeow
Tikki the cat is just about to meow!
Cat meows!
onDidMeow
Tikki the cat did just meow!

What do I have to do to make this work in TypeScript? How is this called exactly? Creating a custom event or creating a custom event handler?

2 Answers 2

11

Something like this:

type Handler<E> = (event: E) => void;

class EventDispatcher<E> { 
    private handlers: Handler<E>[] = [];
    fire(event: E) { 
        for (let h of this.handlers)
            h(event);
    }
    register(handler: Handler<E>) { 
        this.handlers.push(handler);
    }
}

interface WillMeowEvent { }
interface DidMeowEvent { }

class Cat {
    public getName(): string {
        return this.catName;
    }

    public setName(catName: string) {
        this.catName = catName;
    }

    constructor(private catName: string) { }

    public makeMeow() {
        this.fireWillMeow({});
        console.log("Cat meows!");
        this.fireDidMeow({});
    }

    private willMeowDispatcher = new EventDispatcher<WillMeowEvent>();
    public onWillMeow(handler: Handler<WillMeowEvent>) {
        this.willMeowDispatcher.register(handler);
    }
    private fireWillMeow(event: WillMeowEvent) { 
        console.log("onWillMeow");
        this.willMeowDispatcher.fire(event);
    }

    private didMeowDispatcher = new EventDispatcher<DidMeowEvent>();
    public onDidMeow(handler: Handler<DidMeowEvent>) {
        this.didMeowDispatcher.register(handler);
    }
    private fireDidMeow(event: DidMeowEvent) { 
        console.log("onDidMeow");
        this.didMeowDispatcher.fire(event);
    }
}

const myCat: Cat = new Cat("Tikki");
myCat.onWillMeow(event => {
     console.log("Tikki the cat is just about to meow!");
});
myCat.onDidMeow(event => {
     console.log("Tikki the cat did just meow!");
});
myCat.makeMeow();

I'm sure there are libraries that can help. Anyone want to recommend a library in another answer?

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer. Could you please evaluate the meaning between type Handler<E> = (event: E) => void;? Why point to void? The name Handler is just a name and could be anything, right?
The void means that the event handlers (for example, the function with console.log("Tikki the cat is just about to meow!");) do not return a value. Indeed, Handler is just a name that could be changed, but it's a pretty standard term.
I'm sure there are libraries that can help. Anyone want to recommend a library in another answer?. Yes, and without simply giving a link, I have added a complete example of using sub-events to implement what was asked in the original question.
2

When implementing custom events today, it is important that the event is strongly-typed, i.e. the type of data that the event sends is inferred during transpile time, to make the code less error-prone.

There are several libraries today that can do that. Here's is how you can do it with sub-events:

import {SubEvent} from 'sub-events';

class Cat {

    // strongly-typed event that expects a string:
    readonly onMeow: SubEvent<string> = new SubEvent();

    constructor(private catName: string) {
    }

    public makeMeow(message: string) {
        this.onMeow.emit(`${this.catName} ${message}`);
    }
}

const myCat = new Cat('Tikki');

myCat.onMeow.subscribe(message => {
    // message is strongly-typed here;

    console.log(message); //-> Tikki cat is doing something
});

myCat.makeMeow('cat is doing something'); // send the event

Since you are sending the same type of event that differs only by the message, you would normally generalize it into a single event. That's why in the example I simplified it to just one event onMeow.

Also, subscribe is pretty much the standard approach today, which let's you easily cancel the subscription whenever you want, as shown below:

const sub = myCat.onMeow.subscribe(message => {
    console.log(message); // message is strongly-typed here;
});

When the event is no longer needed, you cancel the subscription:

sub.cancel();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.