VueJs 2.0 emit event from grand child to his grand parent component

It seems that Vue.js 2.0 doesn't emit events from a grand child to his grand parent component.

Vue.component('parent', {
  template: '<div>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
  data(){
    return {
      action: 'No action'
    }
  },
  methods: {
    performAction() { this.action = 'actionDone' }
  }
})

Vue.component('child', {
  template: '<div>I am the child <grand-child></grand-child></div>'
})

Vue.component('grand-child', {
  template: '<div>I am the grand-child <button @click="doEvent">Do Event</button></div>',
  methods: {
    doEvent() { this.$emit('eventtriggered') }
  }
})

new Vue({
  el: '#app'
})

This JsFiddle solves the issue https://jsfiddle.net/y5dvkqbd/4/ , but by emtting two events:

  • One from grand child to middle component
  • Then emitting again from middle component to grand parent

Adding this middle event seems repetitive and unneccessary. Is there a way to emit directly to grand parent that I am not aware of?


Vue 2.4 introduced a way to easily pass events up the hierarchy using vm.$listeners

From https://vuejs.org/v2/api/#vm-listeners :

Contains parent-scope v-on event listeners (without .native modifiers). This can be passed down to an inner component via v-on="$listeners" - useful when creating transparent wrapper components.

See the snippet below using v-on="$listeners" in the grand-child component in the child template:

Vue.component('parent', {
  template:
    '<div>' +
      '<p>I am the parent. The value is {{displayValue}}.</p>' +
      '<child @toggle-value="toggleValue"></child>' +
    '</div>',
  data() {
    return {
      value: false
    }
  },
  methods: {
    toggleValue() { this.value = !this.value }
  },
  computed: {
    displayValue() {
      return (this.value ? "ON" : "OFF")
    }
  }
})

Vue.component('child', {
  template:
    '<div class="child">' +
      '<p>I am the child. I\'m just a wrapper providing some UI.</p>' +
      '<grand-child v-on="$listeners"></grand-child>' +
    '</div>'
})

Vue.component('grand-child', {
  template:
    '<div class="child">' +
      '<p>I am the grand-child: ' +
        '<button @click="emitToggleEvent">Toggle the value</button>' +
      '</p>' +
    '</div>',
  methods: {
    emitToggleEvent() { this.$emit('toggle-value') }
  }
})

new Vue({
  el: '#app'
})
.child {
  padding: 10px;
  border: 1px solid #ddd;
  background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <parent></parent>
</div>

NEW ANSWER (Nov-2018 update)

I discovered that we could actually do this by leveraging the $parent property in the grand child component:

this.$parent.$emit("submit", {somekey: somevalue})

Much cleaner and simpler.


The Vue community generally favors using Vuex to solve this kind of issue. Changes are made to Vuex state and the DOM representation just flows from that, eliminating the need for events in many cases.

Barring that, re-emitting would probably be the next best choice, and lastly you might choose to use an event bus as detailed in the other highly voted answer to this question.

The answer below is my original answer to this question and is not an approach I would take now, having more experience with Vue.


This is a case where I might disagree with Vue's design choice and resort to DOM.

In grand-child,

methods: {
    doEvent() { 
        try {
            this.$el.dispatchEvent(new Event("eventtriggered"));
        } catch (e) {
            // handle IE not supporting Event constructor
            var evt = document.createEvent("Event");
            evt.initEvent("eventtriggered", true, false);
            this.$el.dispatchEvent(evt);
        }
    }
}

and in parent,

mounted(){
    this.$el.addEventListener("eventtriggered", () => this.performAction())
}

Otherwise, yes, you have to re-emit, or use a bus.

Note: I added code in the doEvent method to handle IE; that code could be extracted in a reusable way.


Yes, you're correct events only go from child to parent. They don't go further, e.g. from child to grandparent.

The Vue documentation (briefly) addresses this situation in the Non Parent-Child Communication section.

The general idea is that in the grandparent component you create an empty Vue component that is passed from grandparent down to the children and grandchildren via props. The grandparent then listens for events and grandchildren emit events on that "event bus".

Some applications use a global event bus instead of a per-component event bus. Using a global event bus means you will need to have unique event names or namespacing so events don't clash between different components.

Here is an example of how to implement a simple global event bus.


If you want to be flexible and simply broadcast an event to all parents and their parents recursively up to the root, you could do something like:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}