Search code examples
javascriptvue.jsvue-componentvuejs3vue-composition-api

Vue.js 3 Event Bus


How to create Event Bus in Vue 3?


In Vue 2, it was:

export const bus = new Vue();
bus.$on(...)
bus.$emit(...)

In Vue 3, Vue is not a constructor anymore, and Vue.createApp({}); returns an object that has no $on and $emit methods.


Solution

  • 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
    })