Communication between Vue components

When you develop a Vue application you're probably developing multiple components. Many components work independently and are therefore easy to use, but in some occasions you want your components to work together. For example when you want to share data, a state or want to use output from one component in a different component.

Although the documentation of Vue feels complete and clear, it lacks information about implementing communication between components and the best practises. As developer you have multiple options / patterns to achieve communication. In this article we’ll discuss multiple options, including some options you should never use. Before you want to dive in the communication options you should know about SOLID components

Props and Events

A widely used pattern for communication between components uses properties and events. Properties are used by the parent component to pass data to a child component, and a child component emits an event where the parent can act on.

The benefit of communication through props and events is that all communication is present in your html template and therefore clear and simple. Both properties and event-listeners are applied as html-property on the child-components node. A simple setup could almost be done with html.

The downside of communication through props and events is that it's only possible when your components have an Vue child/parent relation. The parent needs direct access to the child node. Of course it's possible to have (multiple) non-Vue nodes between the components, as long as the parent could access the childs node.

<!-- a minimal example of props and events in html -->
<parent>
     <child 
        :customProp=”parentProp” 
        @onCustomEvent=”customEventListener”>
     </child>
</parent>

.sync modifier

With the .sync modifier you could benefits from two-way binding without introducing maintenance issues. With two-way binding it's possible that both the parent and the child component could mutate a variable. When one component changes the value of a variable the other component could react directly. Two-way binding is a heavy process since it's necessary to calculate changes in every Vue cycle. That means that each click, scroll and other common events will trigger a chain of calculations to determine if some components did change a variable. Vue has a solution for, the .sync modifier.

Where two-way binding requires you to wait for an event before you can change a property introduces .sync a shorthand which directly changes a variable for you.

<child
  v-bind:customProp="parentProp"
  v-on:update:customProp="parentProp = $event">
</child>

In the example above you see the classic setup for two-way binding. A variable is passed to a child with a prop, and the child emits an event, so the parent knows the new value of the variable. In the following example you see the magic of .sync:

<child v-bind:customProp.sync="parentProp"></child>

EventBus

One of the most well known messaging pattern is pub/sub, it's a pattern where a component publishes a message but doesn't care which component reacts on this message. Other component(s) can subscribe to these publications. With pub/sub it's not important that the publish and subscribe components know about each other.

The benefit of pub/sub is that you can use this pattern for both child/parent and sibling component relations. Props and events are only possible for child/parent relations, so if you need parent/grandchild or sibling communications pub/sub is the way to go. Another benefit is that with EventBus you are implementing communication between components in JavaScript instead of the template. If you really like to separate concerns EventBus seems like the way to go.

The downside of communication through pub/sub structures is that you could lose the overview on which component is reacting to your component (and vice versa), although it's a key characteristic of this method. Another downside is you can't send an event over the EventBus to a non Vue component. For most SPA this isn't a problem, but for websites where Vue is only to add some dynamic content to the page this could be a problem.

Pub/sub in Vue is possible with its build in $emit and $on methods. A good practice is to use a new Vue-instance specially for EventBus events.

// global EventBus
const EventBus = new Vue();
// first component
Vue.component('component-1', {
  data() {
    return {
      foo: 'bar'
    }
  },
  methods: {
    emitCustomGlobalEvent() {
      EventBus.$emit('custom-event', this.foo);
    }
  } 
});
// second component
Vue.component('component-2', {
  data() {
    return {
      foo: 'bar'
    }
  },
  mounted() {
     EventBus.$on('custom-event', foo => {
         console.log(foo); // should be `bar`    
     });            
  }
});

this.$children and this.$parent

The previous methods to implement some communication between components are good and common examples. They all have a specific use case and are designed for a special occasion / component relation. (child - parent or sibling relations). A bad but widely used pattern (or anti-pattern) is direct communication between components. Doing this goes directly against the philosophy behind components.

With Vue it's possible to directly access related components via the components prototype. A component can access its children by using the JavasScript code $this.children and its parent by using $this.parent. With this method you have direct access to a components state.

You should always avoid this method. As said on top of this page, a component should work on its own, by directly accessing related component you are breaking this pattern. If two components are strongly attracted to each other it's probably the best idea to merge the components to one component as both components are depended on each other.

Conclusion

I really like simple and elegant solutions for every problem as I work in a large team and I'm not always able to explain my code in person. I strongly believe you should never have to explain your code if you write your code KISS. So when I'm in a situation where two of my components are in a parent/child relationship I prefer to use Props/Events messaging to communicate between my components.

When I'm not in that situation I would implement EventBus (or pub/sub) although I don't like that method. Introducing it means that in the near future it's not clear anymore which relationships between components are present in my application.

I will never use .sync or $this.children/$this.parent. .sync is a solution for a problem which doesn't exist in my Vue applications; Two-way binding. And directly accessing related components through $this.children and $this.parent is a anti-pattern so never code which would go to production.