Component Communication in VueJs

Back

Written by Paul Quinn

Making unrelated sections of your VueJs Application/Codebase talk to each other can throw up some interesting approaches. The event bus/publisher-subscriber pattern, despite sometimes receiving bad press, is a great way to achieve this. Lets run through some examples of Component Communication.

Please note, the examples inline are single file components but the Codepens are using inline templates.

If you are familiar with Vue’s components then you hopefully will be aware of Props. Props can be used to pass data down to the child component. This is Parent to Child communication and you will find sometimes the need to pass data down. Vue will sync up any changes from its Virtual DOM with the actual DOM automagically. If you wish for more in depth information head on over to the VueJs Docs Component Page.

But a quick example to showcase this, a parent component wishes to pass a string down:

 


<script>
export default {
  data(){
     return {
         textDown : 'pass me down'
     }
  }
}
</script>

And the child that is expecting a prop called text.

 


<script>
export default {
  props : ['text']
}
</script>

This will result in the output of the text pass me down in the child component. Moving forwards this article is assuming you are experienced with props enough already.


See the Pen Flat minion by weareframework (@weareframework) on CodePen
1

A brief intro to passing data down. So if you ever find yourself in situations where you wish to pass data back up from the child to the parent component, how could you achieve this?

Vue has its own event system and it comes baked in to help you achieve direct Child to Parent communication. In all components you have access to it via this.$emit().

So lets see an example, here is the child component.

Click Me

 


<script>
export default {
  methods: {
    click() {
      this.$emit('clickup', 'a click');
    }
  }
}
</script>

The main thing to take away in the click method is this.$emit and this accepts two properties, the name of the event (in this case clickup) and the data you wish to pass (In the example I am passing a string but often I tend to pass an object/array up).

So now we need to react to this event in the parent component, so lets take a look at that parent component picking up that event.

 

 

 


<script>
export default {
  data() {
    return {
      message: null
    }
  },
  methods: {
    localMethod() {
      console.log('I have been logged');
      this.message = 'I have been logged';
    }
  }
}
</script>

Here is an output of that:


See the Pen Flat minion by weareframework (@weareframework) on CodePen
1

In the html section of this component you will see the child-component is binding to the name of the event as @clickup. When this event is called it will then run the resulting method, in this case the localMethod.

Now this is a nice way to update the parent, say if a local data property needed updating or the parent needs to react to a child changing its local state. It is difficult to say when to use this and when not to but hopefully seeing it in examples can at least show you it in action.

Now the times where you need two unrelated components to communicate will present you to think about how to achieve this? What if a modal to open but can only react to a button in a component that users input data, you wouldn’t want lots of modal components throughout. What if you want to show text in another component after one has recieved data from an AJAX request? How could you communicate to an unrelated component?

Well we can utilise Vue’s event system again to work as a completely separate event communication system. It is happy enough working separately as well as internally.

So first thing to do is to use Vue to create a global event bus and register it to Vue’s prototype chain. This saves you importing it everywhere or even attaching it to window. So here is that.

import Vue from 'vue';
Vue.prototype.$VBus = new Vue();

We could attach it to the window instead, it is up to you, if you wish to attach it to window.

import Vue from 'vue';
window.vBus = new Vue();

But that, I feel, binds you to a global on the window. Whilst attaching it to vue means you can access it in any component via this e.g. this.$vBus.$emit

So lets see an example of emitting an event, same as before it accepts two properties, name of the event and the data to pass.

 


<script>
export default {
  methods: {
    click() {
      this.$vBus.$emit('click', 'a click');
    }
  }
}
</script>

So then in a completely unrelated component you would need to hook into that event and react to it, I find myself registering the event hooks in the components mounted() lifecycle hook to be able to listen to events.

 

 

 


<script>
export default {
  mounted(){
     this.$vBus.$on('click', this.localMethod);
  },
  data() {
    return {
      message: null
    }
  },
  methods: {
    localMethod() {
      console.log('I have been logged');
      this.message = 'I have been logged';
    }
  }
}
</script>

Similar to child to parent communication but the difference is the component reacting to the event registers its listener in a different place (in this case in the mounted hook).

Now a quick example to show these two unrelated components.


  
  

<script>
export default {}
</script>

Here is an output of that:


See the Pen Flat minion by weareframework (@weareframework) on CodePen
1

And the same example with multiple components listening.


See the Pen Flat minion by weareframework (@weareframework) on CodePen
1

Now you have the ability for one component to speak to another component in a different part of your application. It is a handy thing to know but of course beware of overusing this. It could get mucky or used where a different approach might better suit.

If using Vuex of course you could utilise that to alter a global state that all components check for via getters (but that is a different topic altogether).

So these event systems come part and parcel of Vue and can be utilised to achieve component communication in a variety of ways. Now out of the box the event system can do everything you need to achieve unrelated component communication. How about we have a go at a custom communication system ourselves?

The Publisher Subscriber pattern we will use to allow components to subscribe to an event. Then other components can publish an event and all subscribers can then react to that.

First we can write a JavaScript Class to do this.

export default class PubSub {
    constructor() {
        this.handlers = [];
    }
    subscribe(event, handler, context) {
        if (typeof context === 'undefined') {
            context = handler;
        }
        this.handlers.push({
            event: event,
            handler: handler.bind(context)
        });
    }

    publish(event, args) {
        this.handlers.forEach(topic =&gt; {
            if (topic.event === event) {
                topic.handler(args)
            }
        })
    }
}

Two main methods to allow subscription and the ability to publish to those subscribers. Lets now see it within Vue. As before we wish access to this within Vue’s Prototype and so attach it.

import Vue from 'vue';
import PubSub from './PubSub';
Vue.prototype.$pubSub = new PubSub();

Again this then means we get access in every component to the PubSub instance via this.$pubSub.

So then we must subscribe some components to it, a good example would be a bunch of show/hide panel components, they all can open independently. You wish to have a button elsewhere in the application that you would want to have the ability to close all those other panel components with one click.

So here is the Panel Component layout

Show me

Panel text

 

 


<script>
export default {
   data(){
     return {
        show : true
     }
  },
  mounted(){
     this.$pubSub.subscribe('closePanels', this.localMethod, this);
  },
  methods: {
    localMethod() {
      console.log('I have been logged');
      this.show = false;
    }
  }
}
</script>

So here we see that we will use mounted() to subscribe to an event called closePanels. When it is invoked then we will run our localMethod to close the panel.

So we need to an unrelated component that can publish an event to all subscribers, in this case to close them all. So here is an unrelated component with that ability.


  <a class="emit-time">Close</a>

<script>
export default {
  methods: {
    click() {
      this.$pubSub.publish('closePanels');
    }
  }
}
</script>

So this time our click event will run this.$pubSub.publish(‘closePanels’);, which will inform all subscribers to run and thus close them all.

So a component to show all those components plonked together for example purposes. As you can see, they know nothing of each other. But all those panel components need to all close.


  
  
  
  
  

<script>
export default {}
</script>

Here is a codepen to see it in action:


See the Pen Flat minion by weareframework (@weareframework) on CodePen
1

Now its an interesting to see the similarities between the implementation usage of our PubSub class as to the Vue EventBus example above.

We have ran through different ways to utilise communication throughout your VueJs Application. We have used event systems and we have used some patterns to help the way these components communicate. Again it is trying to find the right time to use each and when. Happy reading.

This was quite an Eventful article.