Vue.js 3 Event Bus
As suggested in official docs you could use mitt library to dispatch events between components, let suppose that we have a sidebar and header
which contains a button that close/open the sidebar and we need that button to toggle some property inside the sidebar component :
in main.js import that library and create an instance of that emitter and define as a global property:
Installation :
npm install --save mitt
Usage :
import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt';
const emitter = mitt();
const app = createApp(App);
app.config.globalProperties.emitter = emitter;
app.mount('#app');
in header emit the toggle-sidebar
event with some payload :
<template>
<header>
<button @click="toggleSidebar"/>toggle</button>
</header>
</template>
<script >
export default {
data() {
return {
sidebarOpen: true
};
},
methods: {
toggleSidebar() {
this.sidebarOpen = !this.sidebarOpen;
this.emitter.emit("toggle-sidebar", this.sidebarOpen);
}
}
};
</script>
In sidebar receive the event with the payload:
<template>
<aside class="sidebar" :class="{'sidebar--toggled': !isOpen}">
....
</aside>
</template>
<script>
export default {
name: "sidebar",
data() {
return {
isOpen: true
};
},
mounted() {
this.emitter.on("toggle-sidebar", isOpen => {
this.isOpen = isOpen;
});
}
};
</script>
For those using composition api they could use emitter
as follows :
Create a file src/composables/useEmitter.js
import { getCurrentInstance } from 'vue'
export default function useEmitter() {
const internalInstance = getCurrentInstance();
const emitter = internalInstance.appContext.config.globalProperties.emitter;
return emitter;
}
And from there on you can use useEmitter
just like you would with useRouter
:
import useEmitter from '@/composables/useEmitter'
export default {
setup() {
const emitter = useEmitter()
...
}
...
}
Using the composition API
You could also take benefit from the new composition API and define a composable event bus :
eventBus.js
import { ref } from "vue";
const bus = ref(new Map());
export default function useEventsBus(){
function emit(event, ...args) {
bus.value.set(event, args);
}
return {
emit,
bus
}
}
in component A do:
import useEventsBus from './eventBus';
...
//in script setup or inside the setup hook
const {emit}=useEventsBus()
...
emit('sidebarCollapsed',val)
in component B :
const { bus } = useEventsBus()
watch(()=>bus.value.get('sidebarCollapsed'), (val) => {
// destruct the parameters
const [sidebarCollapsedBus] = val ?? []
sidebarCollapsed.value = sidebarCollapsedBus
})
How to setup a Global Event bus in Vue 3
First the error:
did you try to
export const bus = createApp();
in a different file to avoid circular ref ?Regarding global bus in vue3:
it is recommended to use a global state like what
vuex
provide you or any external package as best practice to communicate between components.and even if you managed to import the created
bus
you still don't have the listener$on
to receive the emitted data. as they deleted it invue3
Vue 3 Event Bus with Composition API
To use an event bus in Vue 3 Composition API, use Vue 3's new provide
api in main.js, and then inject
in any component:
1. Install mitt:
npm install mitt
2. Provide:
main.js
import { createApp } from 'vue';
import App from './App.vue';
import mitt from 'mitt'; // Import mitt
const emitter = mitt(); // Initialize mitt
const app = createApp(App);
app.provide('emitter', emitter); // ✅ Provide as `emitter`
app.mount('#app');
3. Inject
3a. Any Component - Emit an event
import { inject } from 'vue'
export default {
setup() {
const emitter = inject('emitter'); // Inject `emitter`
const mymethod = () => {
emitter.emit('myevent', 100);
};
return {
mymethod
}
}
}
Call mymethod
from a button click or something.
3b. Any Component - Listen for the event
import { inject } from 'vue'
export default {
setup() {
const emitter = inject('emitter'); // Inject `emitter`
emitter.on('myevent', (value) => { // *Listen* for event
console.log('myevent received!', `value: ${value}`);
});
},
}
Console
myevent received! value: 100
How to create a local event bus on Vue 3
There is no event bus in Vue 3. So
$on
will not work. See: https://v3-migration.vuejs.org/breaking-changes/events-api.html#events-apiIt is recommended that you create your event bus with external libraries, unless you want to make use of the supported parent-children event-based communication in which the child emits an event which can only be listened to by the parent. See: https://v3.vuejs.org/guide/component-custom-events.html#custom-events
To implement an event bus for your application, make sure that you are emitting (firing) the events and listening to them with the same instance of the emitter. Else, it won't work.
I preferred
emittery
(https://github.com/sindresorhus/emittery) for event bus with Vue js. It is robust with excellent Typescript support. I also use it on Node js via the Adonisjs framework.Create a useEvent hook
//file: composables/useEvent.ts
import Emittery from 'emittery';
const emitter = new Emittery();
// Export the Emittery class and its instance.
// The `emitter` instance is more important for us here
export {emitter, Emittery};
// Export the Emittery class and its instance
export { emitter, Emittery };
- In any vue component listen to an event
// file: AnyVueComponent.vue
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import {emitter} from '../composables/useEvent'
export default defineComponent({
name: 'ExampleComponent',
components: {},
props: {},
setup() {
onMounted(() => {
emitter.on('event-name', async () => {
// Perform actions. async...await is supported
});
})
return {};
},
});
</script>
- In any vue component emit the event
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import {emitter} from '../composables/useEvent'
export default defineComponent({
name: 'ExampleComponent',
components: {},
props: {},
setup() {
void emitter.emit('event-name');
return {};
},
});
</script>
- Note that you can import the
useEvent
hook into any non-Vue file and it will work. It is just a JS/TS file.
For the Quasar Framework:
- Create a boot file
./node_modules/.bin/quasar new boot EventBus --format ts
- // file: EventBus.ts
import { boot } from 'quasar/wrappers';
import Emittery from 'emittery';
const emitter = new Emittery();
export default boot(({ app }) => {
app.config.globalProperties.$event = emitter;
});
// Export the Emittery class and its instance
export { emitter, Emittery };
- Register with the
quasar.conf.js
file
module.exports = configure(function(/* ctx */){
return {
boot: [
...,
'EventBus',
],
}
}
- In any vue component listen to an event
// file: AnyVueComponent.vue
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import {emitter} from '../boot/EventBus'
export default defineComponent({
name: 'ExampleComponent',
components: {},
props: {},
setup() {
onMounted(() => {
emitter.on('event-name', async () => {
// Perform actions. async...await is supported
});
})
return {};
},
});
</script>
- In any vue component emit the event
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import {emitter} from '../boot/EventBus'
export default defineComponent({
name: 'ExampleComponent',
components: {},
props: {},
setup() {
void emitter.emit('event-name');
return {};
},
});
</script>
Unique EventBus for multiple instances of the same Vue application
In case anyone comes across this problem, I found a solution that works well for me. Thanks to @ChunbinLi for pointing me in the right direction - their solution did work, but passing props everywhere is a bit clunky.
Instead, I used the Provide/Inject pattern supported by Vue: https://v3.vuejs.org/guide/component-provide-inject.html
Some minimal relevant code:
The highest level Grandparent will provide the EventBus,
Grandparent.vue
export default {
provide() {
return {
eventBus: new Vue()
}
}
}
Then any descendant has the ability to Inject that bus,
Parent.vue
export default {
inject: ['eventBus'],
created() {
this.eventBus.$emit('neededEvent')
}
}
Child.vue
export default {
inject: ['eventBus'],
created(){
this.eventBus.$on('neededEvent', ()=>{console.log('Event triggered!')});
}
}
This is still a GLOBAL EventBus, so directionality of events and parental relationship is easy, as long as all components communicating are descendants of the component which "Provided" the bus.
Vue3 Sub-component pass data $on is not a function
I saw from the official website that $on, $off and $once methods are removed from vue 3.
https://v3-migration.vuejs.org/breaking-changes/events-api.html#_2-x-syntax
Vue 3 requires a third-party library to achieve.
index.js
import mitt from 'mitt'
const bus = mitt()
export default bus
App.vue
mounted() {
bus.on('maizuo', (data) => {
console.log(data)
})
}
Mitt event bus, not working inside axios interceptor but works fine in my vue components
app.config.globalProperties
is used to create globals off of Vue component instances (not globals off window
). You're incorrectly trying to use the global event bus in your Axios instance, as the this
in that arrow function is undefined
.
One solution is to factor out the event bus, and import it where needed:
// emitter.js
import mitt from 'mitt'
export default mitt()
// main.js
br>import emitter from './emitter.js'
const app = createApp(App) br>app.config.globalProperties.emitter = emitter
// client.js
br>import emitter from './emitter.js'
⋮
client.interceptors.response.use((response) => {
br> emitter.emit('alert', response.data);
return response;
}, (error) => {
return Promise.reject(error.message);
});
Related Topics
Convert a Number into a Roman Numeral in JavaScript
What Is Right Way to Do API Call in React Js
Ruby with Watir: Handling JavaScript Popup Window
Switch on Ranges of Integers in JavaScript
Space Filling with Circles of Unequal Size
Force Download an Image Using JavaScript
How to Write Asynchronous Functions for Node.Js
How to Pass Parameters in Get Requests with Jquery
Angular 2: Two Backend Service Calls on Success of First Service
How to Parse JavaScript Using Nokogiri and Ruby
How to Detect When a Youtube Video Finishes Playing
Google Maps Places API V3 Autocomplete - Select First Option on Enter
Vue.Js - How to Properly Watch for Nested Data
How to Detect Emoji Using JavaScript
Will Const and Let Make the Iife Pattern Unnecessary
Ruby on Rails 4 JavaScript Not Executed
How to Set State Inside a Useeffect Hook
Error: Require() of Es Modules Is Not Supported When Importing Node-Fetch