EventBusVue 3 TypeScript

EventBus for Vue 3 with TS (TypeScript)

EventBus logic how works Event Bus in Vue, Angular, React mitt() library

What is EventBus? This is a tool wide spread and used in Angular, Vue and React when using state management is redundant. But it’s often used along with state managements like NgRX, MobX, Redux, Vuex, Akita…

What EventBus is doing? When in one 1st place some event occurred the second important place for us get knowledge immediately about this event in the 1st place and could somehow react for this event.

When something happens in 1st place we write emitter, producer of event which we give name as we want:
EventBus.emit(‘ImportantEventOccurred’)

‘ImportantEventOccurred’ – there could be any name you will give to any event which happened in your App and which has some importance and need your reaction to it.

EventBus – it’s just imported name of our EventBus, it could be any too.

.emit(), .on(), .off() – this is the methods of EventBus which are written in the library and they are couldn’t be changed (except the case when you write your own Event Bus library in your App or as separate private or npm library).

In another place we can ‘listen’ if the ‘ImportantEventOccurred’ occurred and if it does we can react to this event in a such way:

EventBus.on(‘ImportantEventOccurred’, () => console.log(‘Some important happenned, we should react to this, do something’));

Also we have in EventBus off() method, which has some small unpleasant secret if we use in Vue 3 mitt() npm library for making this functionality. That secret lays in that for firing .off() method not just the name of Event should be the same as in .emit() and .on() methods, but the callback in .off() method used should be the same as in .on() method, it should be the same function like:

We emit the event when, for example, User clicked on some Submit button:
1. EventBus.emit(‘SubmitEvent’).
Then we listen for this event because we, for example, need in another component (or it could be in the same component) to hide default header and show some custom header:
2. EventBus.on(‘SubmitEvent’, hideDefaultHeader);

const hideDefaultHeader = () => {
isDefaultHeaderShown.value = false;

isCartHeaderShown.value = true; // We assume that there is more than just 2 header so directly write // which header to be shown instead of default one.
}

3. In OnUnMounted or OnBeforeUnmounted hook we delete listenere (subscription) to this ‘SubmitEvent’ for making component clean and for avoiding memory leaking:

EventBus.off(‘SubmitEvent’, hideDefaultHeader);

In this case when .on() and .off() callbacks are the same function .off() method will fire.

But if, for example, instead of previous code for .off() we in this example will use something like:
EventBus.off(‘SubmitEvent’, () => console.log(‘Will never fire));

In Vue 3, you can create an EventBus to facilitate communication between components without using Vuex state management or props. The mitt library is a popular choice for this purpose because it’s lightweight and straightforward.

Native EventBus functionality is deleted from Vue 3 – please, watch this link about default EventBus in Vue 3 for more details.

Below is an example of how to implement and use an EventBus in a Vue 3 project using TypeScript.

How to install and use EventBus with mitt() library in Vue 3 projects with TS?

npm install mitt

In simple example we create folder with the name ‘tool’ or ‘utils’, or ‘libs’ and there create the file EventBus.ts with the next content:

import mitt from "mitt";
export const EventBus = mitt();

This code is quite enough for initializing mitt() library.

But if you want to have advanced types for EventBus, you can use the next approach in EventBus.ts file:

import mitt, { type Emitter } from 'mitt';

type Events<T> = {
[key: string]: string | number | null | undefined | T;
};


export const EventBus: Emitter<Events<unknown>> = mitt<Events<unknown>>();

Here [key: string] – is the key name of Event. In this example we use generic approach for the Event name and T as generic type for payload wich can be passed with .emit(‘SomeEvent’, payload) and instead payload there could be string, number, array of some data, object, null, undefined, whatsever.

And in .on(‘SomeEvent, (payload) => { console.log(‘payload’, payload) }) we will get this data. So we can with EventBus not just inform about some event happenned but to pass with it some data from one place to another in our code in Vue App.

Instead of such generic approach when every Event type is not described (its name and payload =>) like this

type Events<T> = {
[key: string]: string | number | null | undefined | T;
};

you can use detailed approach with specifying every Event name in your EventBus.ts file or you can create separate Events.ts file where can create and then export all Events which has place in your App, it could be comfortable from some point of view if you need to know every event happens in the project and control it.

type Events = {

SubmitCounter: number;

AnotherEvent: string;

};

const eventBus: Emitter<Events> = mitt<Events>();

Here in Events we have ‘SubmitCounter’ event name with payload as number – when we emit this ‘SubmitCounter’ event we pass as the second parameter some number (counter.value for example). And then in .on(‘SubmitEvent’, (payload) => console.log(‘counter value’, payload)) we will get this number as payload.

And here we have ‘AnotherEvent’ which have as payload string.

Of course, we can generate as Events name as [key: string] for not mention every event in the app and we can use generic like T as payload. But if you need to specify any event in your EventBus in Vue 3 project you can use this approach.

Also, as I say above, if you have more then 9 events, you can separate them in the separate file like Events.ts file in the ‘utils’ folder along with ‘EventBus.ts’ file:
export type Events = {
SubmitCounter: number;
AnotherEvent: string;
SomeEvent: boolean;
};

Then in EventBus.ts we have with imported Events:

import mitt, { type Emitter } from 'mitt';
import { type Events } from './Events'

export const EventBus: Emitter<Events> = mitt<Events>();

In main.ts:

app.provide('emitter', emitter);

It will look like in my default setup project in main.ts file:

import './assets/main.css';

import { createApp } from 'vue';
import { createPinia } from 'pinia';

import App from './App.vue';
import router from './router';
import { EventBus } from '@/utils/EventBus';

createApp(App)
.use(createPinia())
.use(router)
.provide('emitter', EventBus)
.mount('#app');

And in HomeView.vue component I have the next working code:
<script setup lang=”ts”>
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
type Ref,
ref,
type UnwrapRef
} from ‘vue’
import { EventBus } from ‘@/utils/EventBus’

const counter: Ref<UnwrapRef<number>> = ref(0);

const multipleCounter = () => {
counter.value *= 100;
console.log(‘Multiplier counter.value’, counter.value);
}

onMounted((): void => {
counter.value++;

EventBus.on(‘SubmitCounter’, multipleCounter);
});

const submitCounter = () => {
EventBus.emit(‘SubmitCounter’);
}

onBeforeUnmount((): void => {
counter.value = 0;
console.log(‘Component is about to be unmounted’);
});

onUnmounted((): void => {
EventBus.off(‘SubmitCounter’, multipleCounter);
console.log(‘EventBus off not working’, EventBus);

EventBus.all.delete(‘SubmitCounter’);
console.log(‘EventBus all delete working’, EventBus);
EventBus.all.clear();
console.log(‘EventBus all clear working’, EventBus);
});
</script>

<template>
<section>
<div class=”counter”>
<p>{{ counter }}</p>
</div>
<button @click=”submitCounter”>Submit</button>
</section>
</template>

<style scoped>
.counter {
display: flex;
justify-content: center;
align-items: center;
}
.counter p {
color: #942929;
font-size: 2rem;
}
</style>

And everything works in this EventBus, except .off() method…

EventBus.off(‘SubmitCounter’, multipleCounter);
console.log(‘EventBus off not working’, EventBus);

EventBus.all.delete(‘SubmitCounter’);
console.log(‘EventBus all delete working’, EventBus);
EventBus.all.clear();
console.log(‘EventBus all clear working’, EventBus);

But instead of .off() not working method you can use
EventBus.all.delete(‘SubmitCounter’); // Where you point out the name of Event which you want to delete.

If you want to delete all Events on the page – you can use also working approach:
EventBus.all.clear();

But be carefull with using .all.clear() if you use Events between several components and when on using one of them your current component will be unmounted (if you use .all.clear() method in onUnMounted hook for example) – in this case the logic of your Events could be broken if it’s required not to be deleted for firing in another component.

Link for example project with Vue 3 setup with TypeScript (.ts files) and EventBus, Vite as bundle is provided with the link here.

Author: Tetiana Nazarova

Leave a Reply

Your email address will not be published. Required fields are marked *