Is there a way to allow events to bubble up when using a component within a component?
My application is a dynamic menu. The dynamic menu is a component (dyn-menu
) and it uses a local component (menu-item
) for each of the <li>
elements. Each <menu-item>
has a click handler associated with it that emits a custom event (with an ID for menu item in the full implementation). But the application doesn't see the events issued by <menu-item>
because they are not bubbled up.
Is there a way to allow the <menu-item>
component, which is local to the <dyn-menu>
component, emit the event and still allow vapp
to see and handle the event?
I'm pretty new to Vuejs so I might be missing something obvious. And it's possible that I'm trying to solve this by using two components and that's not the best way to handle it. Is there is a better way to approach it?
Here's a jsfiddle. You have to remove the @dyn-menu-item-click='itemClick'
line in the <dyn-menu>
template to illustrate that the event doesn't bubble up if the component doesn't handle the event. If that line is removed then <dyn-menu>
doesn't handle the event but vapp
never sees the event either.
There are 4 options I know of
this.$parent
(repetitively) on the child component to access the desired parent and emit the event. (see "Implement your own bubbling event plugin" below)provide
d by the parent and inject
ed in the children.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>
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://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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With