Vuejs - bubbling custom events
There are 4 options I know of
- Re-emit events like you did
- Use
this.$parent
(repetitively) on the child component to access the desired parent and emit the event. (see "Implement your own bubbling event plugin" below) - Use an event bus that is
provide
d by the parent andinject
ed in the children. - Use a Vuex store and push events to an event queue in the child component. Somewhere else in the app, watch that reactive event queue for new elements or just bind it to something.
Implement your own bubbling event plugin
It's very simple. The plugin adds a new $bubble
method that emits events that bubble to their parents. I considered publishing a plugin that does this, but it's so simple that the overhead is not worth it.
// Add this as a Vue plugin
Vue.use((Vue) => {
Vue.prototype.$bubble = function $bubble(eventName, ...args) {
// Emit the event on all parent components
let component = this;
do {
component.$emit(eventName, ...args);
component = component.$parent;
} while (component);
};
});
// Some nested components as an example
// note usage of "$bubble" instead of "$emit"
Vue.component('component-c', {
template: `
<button type="button" @click="$bubble('my-event', 'payload')">
Emit bubbling event
</button>`,
});
Vue.component('component-b', {
template: `<component-c @my-event="onMyEvent" />`,
methods: {
onMyEvent(...args) {
console.log('component-b listener: ', ...args);
},
},
});
Vue.component('component-a', {
template: `<component-b @my-event="onMyEvent" />`,
methods: {
onMyEvent(...args) {
console.log('component-a listener: ', ...args);
},
},
});
var vapp = new Vue({
el: '#app',
methods: {
onMyEvent(...args) {
console.log('root listener: ', ...args);
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a @my-event="onMyEvent" />
</div>
Event bus
The event bus looks like this:
Vue.component('dyn-menu', {
components: {
'menu-item': {
template: '<li @click="itemClick">{{item.text}}</li>',
props: ['item'],
inject: ['eventBus'], // <-- Inject in the child
methods: {
itemClick() {
// Emit the event on the event bus
this.eventBus.$emit('dyn-menu-item-click', ['menu-item dyn-menu-item-click']);
}
}
}
},
// ...
});
var vapp = new Vue({
el: '#app',
data: {
// ...
eventBus: new Vue(),
},
provide() {
return {
// The parent component provides the event bus to its children
eventBus: this.eventBus,
};
},
created() {
// Listen to events on the event bus
this.eventBus.$on('dyn-menu-item-click', this.menuClick);
},
methods: {
menuClick(message) {}
}
})
Working example: https://jsfiddle.net/7vwfx52b/
There are plenty of event bus plugins listed here: https://github.com/vuejs/awesome-vue#custom-events
As of Vue 2.4, components can access their parent's listeners through the $listeners
property. You can set a component to pass through its parent's listeners to particular children by adding an attribute v-on="$listeners"
to the tags for those child elements. See the docs at https://v2.vuejs.org/v2/api/#vm-listeners.
You can also forward specific events with an attribute like: @dyn-menu-item-click=$listeners['dyn-menu-item-click']
.
It's still not true bubbling, but a less verbose way to re-emit events.