Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bubble events on a component subcomponent chain with Vue js 2?

I have my vue application using:

component-parent component that is made of component-child

inside component-parent I have buttons, when someone click a button I want to emit an event in order to be handled by vue and passed to another component

What I did so far:

var vm = new Vue({
    el: '#app',
    methods: {
        itemSelectedListener: function(item){
            console.log('itemSelectedListener', item);
        }
    }
});

Vue.component('component-child', {
    template: ' <span  v-on:click="chooseItem(pty )" >Button  </span>',
    methods: {
        chooseItem: function(pty){
            console.log(pty);
            this.$emit('itemSelected', {
                'priority' : pty
            });
        }
    }
});

Vue.component('component-parent', {
    template: '<component-child  v-for="q in items" ></component-child>'
});

HTML:

<component-parent v-on:itemSelected="itemSelectedListener"  ></component-parent>

It reaches my console.log(pty); line but it seems that this.$emit('itemSelected' wont get through:

console.log('itemSelectedListener', item); // this is not going to be called...

an hint?

should I bubble up the event from child->parent->Vue-instance? ( I also tried that but with no success)

like image 505
koalaok Avatar asked Feb 03 '17 16:02

koalaok


4 Answers

There is one issue with your component-parent template as it tries to render multiple child components. Vue usually requires a single root div inside components therefore you need to wrap it in a div or other tag.

<div>
    <component-child  v-for="q in items"></component-child>
</div>

A second thing to point out is that you emit an event from a child component which is 2 levels down and you listen to it in the root.

Root //but you listen to the event up here 1 level above
 Component 1 //you should listen to the event here
  Component 2 //your try to emit it from here

You have 2 options here. Either emit from component-child listen to that event in component-parent then propagate that event upwards. Fiddle https://jsfiddle.net/bjqwh74t/29/

The second option would be to register a global so called bus which is an empty vue instance that you can use for such cases when you want communication between non child-parent components. Fiddle https://jsfiddle.net/bjqwh74t/30/

Usually between parent and child components you use the events directly by emitting from child and listening in parent with v-on:event-name="handler" but for cases where you have more levels between components you use the second approach.

Doc link for the first case: https://v2.vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events

Doc link for the second case: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication

PS: prefer using kebab-case for event names which means you write with - instead of capital letters. Writing with capital letters can result in weird situations where your event is not caught in the root.

like image 163
Cristi Jora Avatar answered Oct 23 '22 21:10

Cristi Jora


For what it's worth you can use the browser's event API. It requires a little more scripting than Vue's built-in stuff, but it also gets you around these bubbling issues (and is about the same amount of code as creating a "bus", as in the accepted answer).

On child component:

this.$el.dispatchEvent(new CustomEvent('itemSelected', { detail: { 'priority' : pty }, bubbles: true, composed: true });

On parent component, in mounted lifecycle part:

mounted() {
    this.$el.addEventListener('itemSelected', e => console.log('itemSelectedListener', e.detail));
}
like image 33
codeMonkey Avatar answered Oct 23 '22 20:10

codeMonkey


It's a little bit late but here's how I did it:

component-child:

this.$root.$emit('foobar',{...});

component-parent:

this.$root.$on('foobar')
like image 5
behnam Tehrani Avatar answered Oct 23 '22 22:10

behnam Tehrani


In your child component, simply use $emit to send an event to the $root like this:

v-on:click="$root.$emit('hamburger-click')"

Then, in your parent component (eg: "App"), setup the listener in the Vue mounted lifecycle hook like this:

  export default {
    <snip...>
    mounted: function() {
      this.$root.$on('hamburger-click', function() {
        console.log(`Hamburger clicked!`);
      });
    }
  }
like image 3
Geek Stocks Avatar answered Oct 23 '22 20:10

Geek Stocks