Socrates
Socrates

Reputation: 9574

Create custom event within class in TypeScript

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?

Upvotes: 5

Views: 8172

Answers (2)

vitaly-t
vitaly-t

Reputation: 25840

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();

Upvotes: 2

Matt McCutchen
Matt McCutchen

Reputation: 30879

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?

Upvotes: 11

Related Questions