2đź‘Ť
This problem is a good demonstration of the downsides of the event bus pattern:
You, the developer, have to carefully ensure that sender and receiver of all events actually exist at the same moment in time.
In your scenario, that’s not the case:
- When Test1Component emits the event, Test2Component doesn’t exist yet.
- After Test2Component has been created by HomeComponent just a moment later, the event is already “gone”.
My usual disclaimer as a Vue core team member: Don’t use the Event bus pattern.
- [Vuejs]-Vuetify: v-dialog won't close although v-model variable is set to false
- [Vuejs]-ERROR Invalid options in vue.config.js: "baseUrl" is not allowed
2đź‘Ť
Event Bus is just fine but problem is in implementation.
You are very correct in case of you un-comment code
In home component, I tested on your snippet it emit only once event bus test
[console.log]
With events you need to take care this
- You need to define listener. [ make sure you define them before emitting event ]
- Now you just emit event. [ to work we need listener to listen first]
In your case you wrote listener function $on
in created
event of theTest2Component
Now just think for now, You don’t have any listeners at beginning as in home compo
you just commented that listener code .[ its initial step ]
Now when you click on click me
button you are changing
component to new component[Test2Component], It is mounted and its created
event will fire then this listener will start listening to event
but you missed this
this.$emit("changeComponent"); // this is first [ fires/emit ]
this.$bus.$emit("test"); // THEN THIS EMIT
So, When it just start
changing
component fromcompo1 to
compo2test
fired/emitted directly without wait, its not
synchronousit is
AsynchronousIt will not wait to finish all stuff of
changeComponent. It will be emitted immediately [
test`]
Guess what Now when test
emits, at that time Dom operation to add component, ITS NOT DONE YET
, and test
is emitted
So,
No listeners are there
, so noconsole.log
But if you see if, you UNCOMMENT listener
in home function listener is well defined before emit of test event
so it output in console.
I hope you understand it, if not let me know point I will explain it in details
Another thins is that you added $bus in
prototype
so all components has its own bus. instead you can useGLOBAL event Bus
as in example you can see.
in es6 you can do
//bus.js
const bus = new Vue({});
export default bus;
to import it in other components
import bus from './bus.js';
// ... do bus.$on ..
// ... do bus.$emit ..
var $bus = new Vue({});
Vue.component('Test1', {
template: `
<div class="blog-post">
<h3>test1</h3>
<button @click="changeComponent">click me</button>
</div>
`,
methods: {
changeComponent() {
$bus.$emit("chng");
$bus.$emit("test");
}
}
});
Vue.component('Test2', {
template: `
<div class="blog-post">
<h3>test2</h3>
</div>
`,
created() {
$bus.$on("test", () => console.log("test in test2 will not fire as we are little late to listen it"));
}
});
new Vue({
el: '#app',
created: function(){
console.log('created');
$bus.$on("chng", () => this.changeComponent());
$bus.$on("test", () => console.log("test in test2 main/HOME"));
},
data() {
return {
currentComponent: "Test1"
};
},
methods: {
changeComponent() {
this.currentComponent = "Test2";
}
}
});
<!DOCTYPE html>
<html>
<head>
<script> console.info = function(){} </script>
<script src="https://code.jquery.com/jquery.min.js"></script>
<script src="https://vuejs.org/js/vue.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Stack Overflow - Hardik Satasiya</title>
<style>.half{width:50%;float:left;}</style>
</head>
<body>
<div id="app">
<component :is="currentComponent" @changeComponent="changeComponent"></component>
</div>
</body>
</html>
0đź‘Ť
Your test is not correct.
Basically, Test2Component
is not created in the time Test1Component
emit this.$bus.$emit("test");
You saw 2 console.log because codesandbox use hot reload modules, so this.$bus.$on("test", () => console.log("test in test2"));
is still registered if when you change your code.
If you unregister when component destroyed in Test2Component
, test in test2
will never be logged
Test2Component.vue
<template>
<div>
<h1>test2</h1>
<button @click="changeComponent">click me</button>
</div>
</template>
<script>
export default {
created() {
console.log("Component 2 is created");
this.$bus.$on("test", this.testLog);
},
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
methods: {
testLog() {
console.log("test in test2");
},
changeComponent() {
this.$emit("changeComponent");
}
}
};
</script>
<style>
</style>
Demo: https://codesandbox.io/s/2485jw460y
If you comment
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
in component 2, you will see test in test2
is printed many times after few clicks
(if you change the code, please refresh to run from beginning)