Syropia
July 28, 2020

How to Create a Global Event Bus in NuxtJS

In this article, I'm going to show you how simple it is to add a global event bus to your Nuxt application! In essence, all we have to do is create a small plugin, and then register the plugin in our Nuxt configuration file. Let's get started!

What is a global event bus?

Simply put, a global event bus is a way for us to both listen for and emit events anywhere in our application. They're very convenient when a global store (Vuex, Redux, etc) is overkill, but you still want to pass around small bits of data, or listen for an event at any point. You can emit or listen for events from any component, regardless of where it is in your component tree. Handy!

Adding a global event bus in NuxtJS

The first thing we need to do is add a new plugin to our Nuxt application. Go ahead and create a file in the plugins folder called eventBus.js. Type or paste this code into your newly created plugin file.

import Vue from 'vue'

export default (_ctx, inject) => {
  const bus = new Vue()
  inject('bus', bus)
}

There's not much going on here, but let's break it down anyway. You'll notice we're importing Vue, which might strike you as odd, but I'm going to talk a little more about that shortly.

The first thing we do is export a default function for the plugin to run. You'll notice I prefixed ctx with an underscore, which is just a convention I like to use when argument order matters, but you don't actually want to use it. The second parameter inject is what we really want so we include it as well.

Ok, on to the good stuff! On the first line of the function, we new up a barebones instance of Vue. Weird right? However, Vue instances actually provide us with all the capabilities we need for a global event bus, including $emit, and $on functions for emitting and listening for events respectively.

Next we call the inject() function, give it a name of bus, and pass in our empty Vue instance. inject is a function provided by Nuxt that allows us to attach objects and functions to the root Vue instance that Nuxt utilizes under the hood. Cool! We're done in this file, but we still need to register the plugin for Nuxt to recognize it properly.

Whenever you inject an object into Nuxt, it automatically prefixes whatever name you passed to inject with a $ sign. This is a common convention when adding functions directly to Vue instances. Be aware of this when you're using it in your components!

Go ahead and open up your nuxt.config.js file, and add this entry into the plugins array:

{ src: '~/plugins/eventBus.js', ssr: true }

Here we're just passing a path to the plugin, and specifying that it is compatible with server-side rendering. We now have a fully working global event bus for our Nuxt application! 🎉

Practical example: A notification component

Let's build a simple notification component to showcase the power of a global event bus. We'll wire it up to listen for a specific event, and display itself for a few seconds when that event gets triggered from another component. If you'd like to inspect the end result, I've built a CodeSandbox for you to reference.

First we'll create the actual Notification component. Go ahead and create a file called Notification.vue in your components directory. Inside that component, we'll add some simple template code.

<template>
  <div v-show="isShowing" class="notification">
    <span>🔔</span>
    <span>{{ message }}</span>
  </div>
</template>

Next we'll set up the component logic to listen for events from the global event bus, and when we receieve that event, we'll show the notification for 3 seconds.

export default {
  data() {
    return {
      timer: null,
      isShowing: false,
    }
  },
  created() {
    this.$bus.$on('NOTIFICATION', (message) => {
      this.message = message
      this.$nextTick(this.showNotification)
    })
  },
  methods: {
    showNotification() {
      this.isShowing = true
      if (this.timer) clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.isShowing = false
      }, 3000)
    },
  },
}

Alright, let's dissect this a bit. When the component's created hook runs, we use the global event bus's $on method to bind a listener that listens for incoming events called NOTIFICATION. In this case we can assume the event is also going to pass some kind of data — namely a message to display to the user. So in our callback function, we pass message in, and set our own message state to that value.

Once that's done we call a showNotification message inside a $nextTick call. I've previously run into some odd race conditions while doing something similar without $nextTick, so I included it here, but feel free to use it at your own discretion!

The showNotification method is pretty straightforward — we set the isShowing state to true (which our template relies on for showing and hiding the notification in the DOM), and then uses a setTimeout call to set it back to false 3 seconds later.

For good measure we'll add some really simple styles.

<style>
.notification {
  background-color: #c6f6d5;
  border: 1px solid #276749;
  color: #276749;
  font-family: 'Arial', sans-serif;
  position: fixed;
  bottom: 25px;
  right: 25px;
  width: 280px;
  z-index: 999;
  padding: 16px;
}
</style>

Let's open up pages/index.vue, delete everything inside of it, and add our Notification component to it.

<template>
  <div>
    <Notification />
  </div>
</template>
<script>
import Notification from '~/components/Notification'
export default {
  components: {
    Notification,
  },
}
</script>

When the notification is showing it will now look something like this: A basic notification component

Now let's add a button somewhere else in our app to trigger the notification! We'll create another component that contains a text field for setting your custom notification message, and a button to actually trigger it. Create a new file in your components directory called NotificationTrigger.vue. Go ahead and add this inside:

<template>
  <div>
    <input
      type="text"
      placeholder="Enter a notification message..."
      v-model="message"
    />
    <button @click="sendNotification">Send notification!</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      message: '',
    }
  },
  methods: {
    sendNotification() {
      this.$bus.$emit('NOTIFICATION', this.message)
    },
  },
}
</script>

Cool! Now let's hop back into pages/index.vue and add this component right above the Notification component. The whole file should look like this now:

<template>
  <div>
    <NotificationTrigger />
    <Notification />
  </div>
</template>
<script>
import Notification from '~/components/Notification'
import NotificationTrigger from '~/components/NotificationTrigger'
export default {
  components: {
    Notification,
    NotificationTrigger,
  },
}
</script>

Fire up http://localhost:3000, enter some text in the textbox and hit the button. You should see a notification pop up with the text you entered, and it should disppear 3 seconds later. Great work! We've successfully created a practical use case for a global event bus. I've created a CodeSandbox you can check out, to see a live working example.

That's all for now! If you have any questions or comments, feel free to ping me on Twitter, @syropian.