I have a Vue application (which is basically a video player) that uses EventBus to communicate across components which normally cannot communicate easily. This worked perfectly when I was developing the video player, but now I have bundled it using Rollup, and when I try to put multiple instances of the video player on the same page, any Event one instance sends will also be picked up by all the other instances of the application.
Now in hindsight I understand why people don't seem to like the EventBus, but I can't find a great solution to either improve or replace it. I can't name the EventBus instances dynamically, because then the rest of my application won't be informed about the new name. I can't even use something like a videoId in my EventBus listeners to control the uniqueness, because then I will encounter the same problem if a video is on the same page more than once.
Some posts suggest VueX, but my app doesn't need to be stateful and it doesn't seem like a replacement for Event and listeners (though I could be wrong on that.) It seems like a lot of overhead for more functionality than I need. Again, I could be wrong.
I tried to remove as much irrelevant code as possible, but to give an idea of how I implemented my EventBus:
event-bus.js
:
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
MediaPlayer.vue
:
<template>
<div>
<div>
<div id='media-player'>
<end-screen v-if="videoIsFinished"/>
<tap-video
ref="tapVideoRef"
:source='sourceUrl'
:videoId='videoId'
@videoEndHandler='videoEndHandler'
>
</tap-video>
<div id="control-bar-container">
<transition name="slide-fade">
<div v-show='(showControls || !playing)' >
<media-controls
:playing="playing"
:videoLength="videoLength"
/>
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
<script>
import TapVideo from './TapVideo.vue';
import EventBus from './event-bus';
export default {
data (){
return{
playing: false,
showControls: false,
videoLength: 0,
tapVideoRef: null
}
},
mounted() {
this.tapVideoRef = this.$refs.tapVideoRef;
EventBus.$on('videoLoaded', videoLength => {
this.videoLength = videoLength;
});
EventBus.$on('playStateChange', playing => {
this.onPlayStateChange(playing);
});
},
beforeDestroy() {
EventBus.$off(['playStateChange','closeDrawer']);
},
props: ['sourceUrl', 'platformType', 'videoId'],
}
</script>
In case anyone comes across this problem, I found a solution that works well for me. Thanks to @ChunbinLi for pointing me in the right direction - their solution did work, but passing props everywhere is a bit clunky.
Instead, I used the Provide/Inject pattern supported by Vue: https://v3.vuejs.org/guide/component-provide-inject.html
Some minimal relevant code:
The highest level Grandparent will provide the EventBus,
Grandparent.vue
export default {
provide() {
return {
eventBus: new Vue()
}
}
}
Then any descendant has the ability to Inject that bus,
Parent.vue
export default {
inject: ['eventBus'],
created() {
this.eventBus.$emit('neededEvent')
}
}
Child.vue
export default {
inject: ['eventBus'],
created(){
this.eventBus.$on('neededEvent', ()=>{console.log('Event triggered!')});
}
}
This is still a GLOBAL EventBus, so directionality of events and parental relationship is easy, as long as all components communicating are descendants of the component which "Provided" the bus.