Vue 3 Event Bus using the Composition API

Cover image
Photo by Natanael Vieira

In Vue 2, the most common method to organize reusable logic was to use mixins. It wasn't very practical though, as it could introduce naming conflicts with other mixins and reusability was in fact limited. Vue 3's Composition API, however, lets you write and organize reusable logic into smaller chunks of code called composables. Promoting your code to be better organized into self-contained functions, respecting separation of concerns and improving your project's maintainability. It has quickly become my favorite way of organizing reusable logic in my Vue projects.

In this post, I will show you how to leverage the Composition API to add and use an event bus in your project.

First off, we'll create the file useEventBus.js. In this example, I use the library tiny-emitter but feel free to replace it with mitt or whatever event library you might prefer.

// composables/useEventBus.js

import { onBeforeUnmount } from 'vue'
import { TinyEmitter } from 'tiny-emitter';

const eventEmitter = new TinyEmitter()

export default function useEventBus () {
  
  const eventHandlers = []

  onBeforeUnmount(() => 
    eventHandlers.forEach((eventHandler) => 
      eventEmitter.off(eventHandler.event, eventHandler.handler)
  ))

  return {
    onEvent (event, handler) {
      eventHandlers.push({ event, handler })
      eventEmitter.on(event, handler)
    },
    emitEvent (event, payload) {
      eventEmitter.emit(event, payload)
    }
  }
}

useEventBus exports a single function that returns methods for emitting and subscribing to events on the bus. As you can see in the code above, the TinyEmitter instance will be instantiated only once, and will thus be shared by all components that invoke the function. In contrast, the eventHandlers array will be initialized each time. It will keep references to the event handlers that has been added to the event bus by the onEvent method.

We use the function onBeforeUnmount, to hook onto the beforeUnmount lifecycle hook of each component. We do that, to avoid having to manually remove event listeners from our components when they are getting unmounted. This prevents any event handler from behind left behind, dangling.

The next step is to start consuming and emitting events in our components. This is accomplished by importing the useEventBus function and invoking it in the setup methods of our components.

// components/MyComponent.vue

<template>
  ...
</template>
<script>
import useEventBus from 'composables/useEventBus'

export default {
  name: 'MyComponent',
  setup() {
    const { emitEvent, onEvent } = useEventBus()

    return {
      emitEvent,
      onEvent
    }
  },
  created() {
    // Listen to the event 'something' and process the payload
    this.onEvent('something', (payload) => { ... })
  },
  methods: {
    // Invoked from somewhere in the template
    doSomething() {
      this.emitEvent('my-event', 'My event payload')
    }
  }
}
</script>

We invoke the useEventBus function and unpack the methods that we will be using in our component. In this example, we unpack both emitEvent and onEvent since we'd like to be able to both emit and subscribe to events.

Inside the method created, we add an event handler for the event something. We can use this event handler to process the payload and update our component, among other things. Thanks to the onBeforeUnmount hook that we added in the useEventBus function, this event handler will be automatically removed when the component gets unmounted.

Invoking the doSomething() method from the template or within the component itself, would emit the event 'my-event' with the payload "My event payload".



I hope this demonstrates how easily you can create and organize reusable logic in your Vue projects. I have created a demo here where you can see the useEventBus function in action. The source code is available here on GitHub.

Thanks for reading!