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, share a state or want to use output from one component in an other component.

Although the documentation of Vue feels complete and clear, it lacks information about implementing communication between components and its best practices. 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 is based on 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 properties 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.

The downside of communication through props and events is that it's only possible when your components have a direct Vue parent/child 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 can access the child's node.
Note; when you use slots, you don't have direct access from parent to child component.

<!-- 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 benefit from two-way binding without introducing maintenance issues. With two-way binding it's possible that both the parent and the child component share a data-variable. When one component changes the value of that 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. This 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, the .sync modifier introduces 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 know which components are listening to 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. They should be useable independently.

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

The downside of communication through pub/sub structures is, 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 lives next to some jQuery or vanilla JavaScript code this could be an issue.

Pub/sub in Vue is possible with its build-in $emit and $on methods. A good practice is to use a new Vue-instance specifically 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 communication between components are good and common used examples. They all have a specific use case and are designed for a special occasion and relation. A bad but widely used pattern (or anti-pattern) is direct communication between components. Doing this goes directly against the philosophy behind (SOLID)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 the related component's state.

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

However, there is one valid use case; when you have a parent component with children components in its slot. The Vue architecture doesn't let you to pass props and react on events when you use slots. When using slots and knowing exactly which children are present, and you really know which parent you have as a child component it is a good case to use this.$parent and this.$children. Make sure you keep a separation between components, don't call methods on children of the parent directly, but try to use events as you would normally do. You could pass events by emitting an event on the 'other' component. For example when you want to send an event to your child component use this.$children[0].$emit('yourEvent'). And the other way around you could use this.$parent.$emit('yourEvent'). Keep in mind that eventListeners in your parent or child component will not work. In your component you should add eventListeners. You could do it this way: this.$on('yourEvent', myCalback());

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 although I don't like that pattern. 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 and try not to use $this.children/$this.parent. .sync is a solution for a problem which doesn't exist in my Vue applications;