Donkarnash
Donkarnash

Reputation: 12835

How to use a global event bus in vue.js 1.0.x?

I am trying to create a event bus with an empty new Vue instance. The app is large enough to be split into multiple files for components. As an example my app is structured as :

main.js

import Vue from vue;  
window.bus = new Vue();
Vue.component('update-user', require('./components/update-user');
Vue.component('users-list', require('./components/users-list');
Vue.component('edit-user', require('./components/edit-user');
Vue.component('user-address', require('./components/user-address');

new Vue({
    el:'body',
    ready(){

    }
}); 

components/update-user.js

export default{
    template: require('./update-user.template.html'),
    ready(){
        bus.$emit('test-event', 'This is a test event from update-user');
    }
}

components/users-list.js

export default{
    template:require('./users-list.template.html'),
    ready(){
        bus.$on('test-event', (msg) => { console.log('The event message is: '+msg)});
        //outputs The event message is: This is a test event
    }

components/edit-user.js

export default{
    template:require('./edit-user.template.html'),
    ready(){
        bus.$on('test-event', (msg) => {console.log('Event message: '+msg)});
        //doesn't output anything
        console.log(bus) //output shows vue instance with _events containing 'test-event'
    }
}

components/user-address.js

export default{
    template:require('./user-address.template.html'),
    ready(){  
        bus.$on('test-event', () => {console.log('Event message: ' +msg)}); 
        //doesn't output anything
        console.log(bus) //output shows vue instance with _events containing 'test-event'
    }
}  

index.html

...
<body>
    <update-user>
        <users-list></users-list>
        <edit-user>
            <user-address></user-address>
        </edit-user>
    </update-user>
</body>
...

My question is that why does bus.$on work in the first child component only? Even if I remove the listener from <users-list>, none of the other components are able to listen to the event i.e console.log() with bus.$on doesn't work in any component below/after <users-list> i.e. the immediate child component.
Am I missing something or where am I doing wrong?
How to get this working so that any child component at any depth can listen to an event emitted from even the root component or any where higher up in the hierarchy and vice-versa?

Upvotes: 3

Views: 1701

Answers (3)

Donkarnash
Donkarnash

Reputation: 12835

I figured it out and got it working. Posting here to be of help to someone else who hits this question.

Actually there's nothing wrong with the implementation I have mentioned above in the question. I was trying to listen to the event in a component which was not yet rendered (v-if condition was false) when the event was fired. So a second later (after the event was fired) when the component was rendered it could not listen for the event - this is intended behavior in Vue (I got a reply on laracasts forum).

However, I finally implemented it slightly differently (based on a suggestion from Cody Mercer as below:

import Vue from vue;

var bus = new Vue({});

Object.defineProperty(Vue.prototype, $bus, {
    get(){
        return this.$root.bus;
    }
});  

Vue.component('update-user', require('./components/update-user');
Vue.component('users-list', require('./components/users-list');
Vue.component('edit-user', require('./components/edit-user');
Vue.component('user-address', require('./components/user-address');

new Vue({
    el:'body',
    ready(){

    },
    data:{
       bus:bus
    }
});   

Now to access the event bus from any component I can use this.$bus as

this.$bus.$emit('custom-event', {message:'This is a custom event'});  

And I can listen for this event from any other component like

this.$bus.$on('custom-event', event => {
    console.log(event.message);  
    //or I can assign the message to component's data property  
    this.message = event.message;  

    //if this event is intended to be handled in other components as well
    //then as we normally do we need to return true from here  
    return true;
});

Upvotes: 3

Ito Pizarro
Ito Pizarro

Reputation: 1607

$emit dispatches the event within the scope of the instance — it doesn't propagate to parents/children. $broadcast will propagate to child components. As mentioned in @Jeff's answer, the intermediate component event callbacks have to return true to allow the event to continue cascading to [their] children.

        
var child = Vue.extend({
  template: '#child-template',
  data: function (){
  	return {
    	notified: false
    }
  },
  events: {
  	'global.event': function ( ){
    	this.notified = true;
      return true;
    }
  }
});

var child_of_child = Vue.extend({
  data: function (){
    return {
      notified: false
    }
  },
  template: '#child-of-child-template',
  events: {
  	'global.event': function ( ){
    	this.notified = true;
    }
  }
});

Vue.component( 'child', child );
Vue.component( 'child-of-child', child_of_child );

var parent = new Vue({
    el: '#wrapper',
    data: {
      notified: false
    },
    methods: {
    	broadcast: function (){
        this.$broadcast( 'global.event' );
      },
      emit: function (){
        this.$emit( 'global.event' );
      }
    },
    events: {
    	'global.event': function (){
      	this.notified = true;
      }
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="wrapper">
  <h2>Parent notified: {{ notified }}</h2>
  <child></child>
  <button @click="broadcast">$broadcast</button>
  <button @click="emit">$emit</button>
</div>

<template id="child-template">
  <h5>Child Component notified: {{ notified }}</h5>
  <child-of-child></child-of-child>
</template>
<template id="child-of-child-template">
  <h5>Child of Child Component notified: {{ notified }}</h5>
</template>

Upvotes: 0

Jeff
Jeff

Reputation: 25211

Event propagation stops when a listener is triggered. If you want the event to continue on, just return true from your listener!

https://vuejs.org/api/#vm-dispatch

bus.$on('test-event', () => {
  console.log('Event message: ' +msg); 
  return true;
}); 

Upvotes: 1

Related Questions