Global Bus v Page Bus ?
-
I’ve been using the global event bus to create decoupled components for a PWA.
It works great until there are multiple instances of a page in the browsing history - each instance continues to receive and process events.
Options
-
Ignore it - just make sure event handlers are lightweight and idempotent.
-
Introduce scope into the global bus (e.g. override $emit and $listen to prefix events with id of root component).
-
Create a “page-bus” for each instance.
-
Do something else.
Any else had experience of this?
-
-
Keeping this here for posterity but no longer works in Vue 3.
A solution that does work in Vue 3 is given in subsequent post (see reply below dated March 9th 2021).
For anyone interested this works as a plugin solution. Please shout if you have a better approach or can contribute to improving this one; e.g.:
- Locating the page root is an educated guess and may not fit all cases.
- Desirable to use this.$page.$emit rather than this.$$emit.
- A better solution using Vue extends may exist.
/* pagebus.js Using the global event bus to decouple components in a PWA works great until there are multiple copies of a page instance in the browsing history; each instance continues to receive and respond to events - especially troublesome when one of the components fetches data from the server (the data is fetched multiple times). An interim workaround is provided here by propagating events and listeners to the 'page' root not the 'app' root. TO DO: 1. Locating the page root is an educated guess and may not fit all cases. 2. Desirable to use this.$page.$emit rather than this.$$emit. 3. A better solution using Vue extends may exist. */ import Vue from 'vue' const DEBUG = true export default { install (Vue, options) { Vue.mixin({ methods: { $$page: function () { // find the page root (educated guess) let x = this; do { x = x.$parent; } while (x && x.$parent && x.$parent.$parent !== x.$root); return x }, $$on: function (ev, ...args) { // propagate to page root component DEBUG && console.log('-> pagebus: on', this._uid, ev, args) return (this.$$page() || this).$on(ev, ...args) }, $$emit: function (ev, ...args) { // propagate to page root component DEBUG && console.log('-> pagebus: emit', this._uid, ev, args) return (this.$$page() || this).$emit(ev, ...args) } } }) } }
-
A Problem
- Vue 3 instances no longer implement the event emitter interface.
- Recommendation is to use mitt.
- See: https://github.com/vuejs/rfcs/blob/master/active-rfcs/0020-events-api-change.md
A Solution
- Load the code below as a boot file (e.g.
pagebus.js
). - Using
this.$on
is detected as error so am using$$on
instead.
// file boot/pagebus.js import { boot } from 'quasar/wrappers' import mitt from 'mitt' export default boot(({ app }) => { app.use({ install: function (Vue, options) { const pagebus = mitt() app.config.globalProperties.$$on = pagebus.on app.config.globalProperties.$$emit = pagebus.emit } }) })
If you wish to debug:
... app.config.globalProperties.$$on = function (ev, ...args) { console.log(ev, args) return pagebus.on(ev, ...args) } ...