Vue.js: Global event bus is failing to catch the event - vue.js

I am trying to create a global event bus to be emitted from Source.vue to be caught in Destination.vue. The latter component has to run a function when the event is caught.
Source.vue:
.then(() => {
eventBus.$emit("fireMethod");
})
Destination.vue:
updated() {
eventBus.$on("fireMethod", () => {
this.reqMethod();
});
}
main.js:
export const eventBus = new Vue();
I have also imported the main.js file in both of the components. But that doesn't seem to work as the event is not caught at all. What's wrong here?
Thanks in advance

The updated hook runs after data changes on your component and the DOM re-renders. Hence on the initial render your event listener is not registered as the event is not triggered at that time.
Kindly go through this fiddle to get a clear idea about vue components life cycle.
Hence in Destination.vue:
Your event hook should be mounted and not updated.
mounted() {
eventBus.$on("fireMethod", () => {
this.reqMethod();
});
}

To register global event bus you can use this in your main.js
Vue.prototype.$eventHub = new Vue(); //global event bus
And then in your components use it like
//on emit
this.$eventHub.$on('your-event',function(){});
//to emit
this.$eventHub.$emit('your-event',data);

Related

Using the mitt-library in Vue with Webpack hot-reload causes problems?

In my Vue3 app, I'm using the mitt eventbus library to emit and receive events between components.
I put this in onMounted of a list component that needs to refresh:
mitt.on("list_refresh", (evt) => {
refresh();
});
In another component that contains the list-component as a child (or grandchild), I do this in a method:
mitt.emit("list_refresh", {});
This works ok, but while developing with hot-reload on, the events seem to be emitted multiple times, as if they're created extra each time the app reloads, instead of overwriting the old ones.
When I reload the entire page in the browser, it works fine again.
Any idea to prevent this?
It looks like your component is missing a corresponding off() call to remove the event listener. During hot reload, the current component instances unmount, and new ones mount; so if you're not removing current event listeners, you'll just pile on new event listeners. To resolve the issue, use the onUnmounted hook to remove the event listener when the component is removed from the DOM.
Also, make sure to pass cached function references (instead of inline functions) to mitt.on() and mitt.off() to ensure the given event listener lookup succeeds in mitt.off():
// mitt.on('list_refresh', () => refresh()) ❌
mitt.on('list_refresh', refresh) ✅
mitt.off('list_refresh', refresh)
Your setup() should look similar to this:
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
const refresh = () => { /*...*/ }
onMounted(() => mitt.on('list_refresh', refresh))
onUnmounted(() => mitt.off('list_refresh', refresh)) 👈
}
}

Whats wrong with my eventBus implementation?

I want to trigger a function from another component which is not parents and child relationship. i try to use eventbus, but i am not sure am i implemented it correctly, since the function is not working. i have created a boot file eventBus.js and added it into the quasar config, but its still not working. Anyone can help me to check which part did i implemented wrongly?
eventBus.js
import Vue from 'vue'
const eventBus = new Vue();
export default eventBus
Vue.prototype.$eventBus = eventBus;
below is the function that i want to get from this page, (this.getSchedule(), it is working perfectly on current page).
mounted(){
this.$eventBus.$on("refreshListSchedule", ()=>{
scheduleDate = this.scheduleDate
console.log(scheduleDate)
console.log("Testing successed")
this.getSchedule()
})
},
The page that i want to trigger it
this.$eventBus.$emit('refreshListSchedule')
It is better if you manually import from eventBus.js in each component where you want to emit or subscribe for events - don't rely on Vue.prototype.
And don't forget to unsubscribe in the beforeDestroy hook.

How to force vue to wait for async <script> to run before mounting components

I have an async js script which is loaded at the top of index.html in my vue project. This script exposes several functions into the window object which I would like to be able to call. I would like to be able to call these functions in the mounted() lifecycle hook, but the async function appears to complete only after mounted has finished. Is there a way I can force the vue instance to wait for all <script> to complete before mounting the root component?
According to this issue in Github https://github.com/vuejs/vue/issues/7209 it seems that the async hooks in Vue lifecycle mounted() created() etc, is only for the ability to call async functions. but the lifecycle itself is synchronous.
Here are some ideas to manage this problem:
You can wrap your component with v-if and render it just as soon as your data is ready.
If you render a component by router - you can use the lifecycle of Vue router. and call your async function before enter to Vue page. do it like this:
export default {
beforeRouteEnter (to, from, next) {
// called before the route that renders this component is confirmed.
next()
}
}
this next() function calls the first Vue lifecycle hook...
full tutorial: https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
If anyone is interested, here is how I solved my problem:
I modified the js file that the <script> was referencing to set global.initComplete=false initially, and when the script was complete, I marked it as true.
For main.js file, I used:
import Vue from 'vue'
import App from './App.vue'
function init() {
let vm = await new Vue({
render: h => h(App),
})
if(!global.initComplete) {
setTimeout(init, 50)
} else {
vm.$mount('#app')
}
}
init()

Dynamically listen to custom event on parent component

In my custom library I have several components which run ajax requests, and when a given request fails the component emits an error event.
Then, in my main component I want to listen to all error events emmited and run the method handleErrors, but for that I have to go on every component and add #error="handleErrors".
Is there a way to configure my main component to catch error events dynamically and call handleErrors without going on each component and adding it? Preferrably changes to the main component only.
You can use the EventBus system in Vue instance. Actually EventBus is a different Vue instance your Main Vue instance. You make your own Event bus system.$emit, $on and $off events.
event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
and now you are ready to use.
some-component.vue
// Import the EventBus.
import { EventBus } from './event-bus.js';
// Send the event on a channel (eventName) with a payload (the click count.)
EventBus.$emit('eventName', this.clickCount);
other-component.vue
// Import the EventBus.
import { EventBus } from './event-bus.js';
// Listen for the eventName event and its payload.
EventBus.$on('eventName', clickCount => {
console.log(clickCount)
});
// Stop listening.
EventBus.$off('eventName');
more information and example
https://alligator.io/vuejs/global-event-bus/
Figured a way to do it without refactoring the individual components: just injected the listener in every component using a mixin.
The result:
// Before starting up the main instance:
Vue.mixin({
created: function() {
this.$on('error', function(error) {
// Then I handled the errors here.
);
}
});

Vue.js: how to use the afterEnter hook with an async component

I would like to use JS Hook as described here. Specially, I want to use the afterEnter hook with an async component.
This is my async component:
Vue.component('example', function(resolve, reject){
let data = {
text: 'test data',
};
$.post('http://example.com', data, function(r){
r = JSON.parse(r);
if( r.success ) {
resolve({
template: r.data,
afterEnter: function(el, done){
console.log('test');
}
});
}
});
});
This is what the ajax call gets from the server, and it's what is passed to the template in r.data.
<transition v-on:after-enter="afterEnter"></transition>
These are the two errors that I get.
[Vue warn]: Property or method "afterEnter" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
[Vue warn]: Invalid handler for event "after-enter": got undefined
Is it possible to use JS hooks with async components? And if not, how should I approach this? My objective is to run custom JS after Vue (and/or vue-router) inject the component template onto the page, so that I can initiliaze image sliders and whatnot. It is important that my custom JS fires every time the component is navigated to, and not only on the first load.
Thank you.
That warning means that Vue is looking for (but unable to find) a property or method named "afterEnter", which you reference in your template. You have defined afterEnter in your resolve function as if it is a lifecycle hook, but it needs to be one of your Vue instance's methods.
So, your resolve function should look like this:
resolve({
template: r.data,
methods: {
afterEnter: function(el, done) {
console.log('test');
}
}
});