Understanding Components Communication in Vue 2.0

Understanding Components Communication in Vue 2.0

The way components communicate, in Vue, is through props and custom events. And with Vue 2.0 release, this system has changed quite a bit — it has actually gotten simpler!

But still, this transition might not feel so obvious to many developers. So, in this article, I’d like to show you the different cases in which components communicate, and how to handle each one properly.

Three cases of components communication

Let’s start first by defining the three cases that components communicate with one another:

  • From a parent component to its direct children. And this happens by passing data to the child via props! Nothing more, nothing less!
  • From a child to its direct parent.
  • Globally, between different components. This includes child components with their ancestors (non-direct parents). Or parent to its non-direct children. Or between sibling components.

I’ve already explained how the first case works. So, let’s get into the other two cases.

Parent-Child communication

It’s called parent-child communication because they are directly related to each other — note that we’re talking here about the second case, which is from child to its direct parent.

To accomplish this kind of communication, you have to do two things:

  1. First, Fire an event from the child component using $emit(eventName, [data]).
  2. Listen to it on the parent using the v-on:event-name directly in the template where the child component is used.

Here’s a quick example:

Let’s say you’ve built your own custom dropdown component that contains a bunch of options which you pass from the parent. When an option is selected in that dropdown, it should emit an event to its parent, so it can do its stuff.

Here’s what happens when an option is selected in Dropdown.vue:

methods: {
  selected () {
    // assume 'this.selectedOption' is defined in the data object
    this.$emit('option-select', this.selectedOption)
  }
}

Now in the parent component — let’s call it NavBar.vue — we’ll have something like this (note that I’m using the shorthand for v-on which is @):

<template>
  <div class="nav-bar">
    <!-- other elements -->
    <dropdown
      :options="someOptions"
      @option-select="onOptionSelect"
    ></dropdown>
  </div>
</template>

<script>
export default {
  methods: {
    onOptionSelect (option) {
      // handle it here
    }
  }
}
</script>

Listening to native events

To make it easy to differentiate between listening to custom events and native events (click, keyUp, etc), Vue 2.0 lets us specify that using the .native modifier. Here’s an example:

<my-component @click.native="onComponentClick"></my-component>

This means, when <my-component> is clicked, we’ll treat it as any other normal event on any element (such as <button>).

This also means, you can define your own custom click event on that component without any conflicts — maybe you want that event to be emitted when the user clicks on a particular part in that component — not the whole component.

In this case you don’t have to use that modifier:

<!— listening to click event which was emitted from my-component —>
<my-component @click="onComponentClick"></my-component>

Non Parent-Child communication

In other words, global component communication. In Vue 1.0, we were handling these kind of events using the two instance methods:

  1. $dispatch: to fire an event that propagates upward along the parent chain.
  2. $broadcast: to broadcast an event that propagates downward to all descendants (all children).

Now in Vue 2.0, those methods are deprecated; there are several reasons for that:

  1. As the component’s tree structure gets larger, it becomes very hard to maintain and reason about. For each modification you do, you have to check if those events are still passed to their handlers correctly — which sometimes requires some workarounds until everything is back to normal. This means, in other words, events are tightly coupled with the component’s tree, which is always bad!
  2. Attaching multiple listeners isn’t that intuitive, because the normal behavior is to stop propagation on the first handler, unless you return true from it.
  3. There’s no explicit way to pass events to sibling components — a workaround we were using is this: this.$root.broadcast('event-name'), which doesn’t feel good, does it?

So, what’s the solution instead? Well, the solution is get rid of those methods altogether, and handle all the communications through what we call the event hub (or some call it event bus).

In its simplest definition, it’s an empty Vue instance that you use globally to fire and listen for events anywhere you want in the component’s tree.

So, obviously, like any other vue instance, we have these methods available for that:

  • eventHub.$emit(eventName) to emit an event.
  • eventHub.$on(eventName) to listen for an event.
  • eventHub.$off(eventName) to remove event listeners.

As I just mentioned, this eventHub is an empty Vue instance which you’d create like this: const eventHub = new Vue().

This needs to be globally accessible

For everything to work correctly, you need to make sure that this eventHub is accessible globally. And this depends on how your application is structured.

If you’re building a simple non-modular app, you can attach that instance to the browser’s window object.

However, for module-based applications that use bundling tools like Webpack or Browserify, you can do that by exporting that instance from a certain file and then import it wherever you need it.

// src/shared/EventHub.js

export default new Vue()

Now in other components, you would import it like this:

import eventHub from 'src/shared/EventHub'

Example

Let’s say, you have this component’s tree:

component-one
  => component-a, component-b
component-two
  => component-c, component-d

In this example, we have two main components named component-one and component-two where each one contains other nested components, as described above.

Let’s imagine that we want to pass an event from component-b to component-c.

To do that, we first need to emit that event from component-b:

import eventHub from 'src/shared/EventHub'

// …

methods: {
  doSomething () {
    eventHub.$emit('component-b-did-something', someData)
  }
}

Now, in component-c, you would listen to it like this:

import eventHub from 'src/shared/EventHub'

// …

mounted () {
  eventHub.$on('component-b-did-something', (someData) => {
    // handle it however you want.
  })
}

As simple as that! The great thing about this approach is that you’re no longer concerned about the components tree structure. In other words, events can be emitted from any component and handled by any other component regardless of their relationship.

One last note

Although it seems that this is the ultimate solution for any complex components communication, it is not! Because imagine how unmaintainable this can become as your app grows more in complexity.

Luckily, for those cases we have a dedicated solution for that, which is Vuex. If you’re building any complex SPA and you’re not using Vuex yet, I highly encourage you to take the time to learn about it as it’ll make your life much easier in those case.

In conclusion

To finish up this post, let’s review the different cases of components communication:

  1. From parent to direct children, no custom events required; just use props.
  2. From children to their direct parent, use $emit(eventName) in children and the directive v-on:event-name in the parent.
  3. Globally (from any component to any component), use the global event bus. Or use Vuex for more complex cases.
Vue
Taha Shashtari

About Taha Shashtari

I'm a freelance web developer. Laravel & VueJS are my main tools these days and I love building stuff using them. I write constantly on this blog to share my knowledge and thoughts on things related to web development... Let's be friends on twitter.