Mahmud Adam
Mahmud Adam

Reputation: 3579

Vue.js global event bus

I am trying to create a global event bus so that two sibling components can communicate with each other. I have searched around; however, I cannot find any examples of how to implement one. This is what I have so far:

var bus = new Vue();

Vue.component('Increment', {
  template: "#inc",
  data: function() {
   return ({count: 0})
  },
  methods: {
    increment: function(){
      var increment = this.count++
      bus.$emit('inc', increment)
  }
 }
})

Vue.component('Display', {
  template: "#display",
  data: function(){
  return({count: 0})
  },
 created: function(){
   bus.$on('inc', function(num){
   alert(num)
   this.count = num;
  });
 }
})


vm = new Vue({
 el: "#example",
})

I created my templates like so: http://codepen.io/p-adams/pen/PzpZBg

I'd like the Increment component to communicate the count to the Display component. I am not sure what I am doing wrong in bus.$on().

Upvotes: 16

Views: 21550

Answers (4)

Jeff
Jeff

Reputation: 25231

The problem is that within your bus.$on function, this refers to the bus. You just need to bind the current Vue instance to that function using .bind():

bus.$on('inc', function(num){
 alert(num)
 this.count = num;
}.bind(this));

You should also check out https://github.com/vuejs/vuex if you want to manage global application states.

EDIT: Since this page seems to get a lot of clicks I want to edit and add another method, per ChristopheMarois in the comments:

EDIT: In effort to make this answer a little clearer, and so future readers don't need to read comments here's what's happening:

Using a fat arrow like below binds the lexical scope of 'this' to the component instead of to the event bus.

bus.$on('inc', (num) => {
 alert(num);
 this.count = num;
});

Or removing the alert:

bus.$on('inc', (num) => this.count = num);

Upvotes: 35

adi518
adi518

Reputation: 862

How about this? Assume Vue.js 2.

Create a reusable Event-Bus component and attach it to Vue via plugin pattern:

// ./components/EventBus.vue
import Vue from 'vue'
export const EventBus = new Vue()

// ./plugins/EventBus.js
export default {
  install(Vue) {
    const { EventBus } = require('../components/EventBus')
    Vue.prototype.$bus = EventBus
  }
}

// ./main.js
import EventBus from './plugins/EventBus'
Vue.use(EventBus)

Then, you can do anywhere in your code: this.$bus.$emit('some-event', payload)

As a side note, try to utilize the Event-Bus pattern as last resort.

Upvotes: 1

Syed
Syed

Reputation: 16543

This is answered long back, here is my solution using in vue.js-2

main.js

import Vue from 'vue'
import App from './App'

export const eventBus = new Vue({
  methods:{
    counter(num) {
      this.$emit('addNum', num);
    }
  }
});

new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
});

comp1.vue

//Calling my named export
import { eventBus } from '../../main'
<template>
  <div>
    <h1>{{ count }}</h1>
    <button @click="counterFn">Counter</button>
  </div>
</template>

<script>
  import { eventBus } from '../../main'

  export default {
    name: 'comp-one',
    data() {
      return {
        count: 0
      }
    },
    methods: {
      counterFn() {
        eventBus.counter(this.count);
      }
    },
    created() {
      eventBus.$on('addNum', () => {
        this.count++;
      })
    }
  }
</script>

Upvotes: 3

tomyam
tomyam

Reputation: 389

As you write ES5 JavaScript you have to be aware of the fact that what you refer to by using the this keyword might change, according to the scope, it is called from.

A useful metaphor to get your head around the this concept is to think of the curly braces in ES5 as fences, that contain/bind its own this.

When you use this in the callback function of your event bus, this does not refer to your Vue component, but the bus object, which has no count data, so the data you expect to update doesn't.

If you have/want to write ES5 syntax a common workaround (besides binding this as suggested by the accepted answer) is to assign the this keyword to a variable like so:

created: function(){
  var self = this;
  bus.$on('inc', function(num){
    alert(num)
    self.count = num;
  });
}

If you can write ES6, do so whenever possible. You can always compile/transpile down to ES5 with Babel. The accepted answer shows you how by using arrow functions.

Arrow functions work in that case because they do not bind their own this.

To stick with the fence metaphor: imagine the ES6 arrow poking a hole in your function fence, so the outer this can pass through and you can call this as intended.

To learn more about ES6 arrow functions visit: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Upvotes: 4

Related Questions