vue component is not passing events through - vue.js

I have three nested components
<parent #event="handle">
<inbetween #event="$emit('event')">
<child> // emits event
</child>
</inbetween>
</parent>
so, when a child emits the event, I have to add that annoying part #event="$emit('event')" to the component inbetween, otherwise parent does not receive any event.
I hope it is not supposed to work that way. I wonder what could be wrong with it?

Yes thats how its supposed to work. Events go only from child to parent, it does not go from child up to grand parent. Therefore you have to emit the event from both child and inbetween component.
If you want to avoid this method you can use Event Bus:
https://medium.com/#andrejsabrickis/https-medium-com-andrejsabrickis-create-simple-eventbus-to-communicate-between-vue-js-components-cdc11cd59860
alligator.io/vuejs/global-event-bus

This is actually intentional. The reasoning is, when looking at the code for one component and you see that it's listening to an event, you can then look at the template to see where that event is coming from. If events could reach a component arbitrarily deep, it would be harder to figure out how and from where that event is being triggered.
However, Vue used to have a way of doing what you want to do, through the methods $broadcast and $dispatch, and they were eventually removed for the reasons talked about here. Here's a page from the docs which explains why, along with possible solutions, such as using a global event bus, or a centralized state management solution such as Vuex.

Vue custom events don't bubble.
The recommended way to handle ancestor/sibling communication in complex cases is using Vuex. If you have simple needs you can create Vue instance to use as event hub.
You would create a global variable:
var eventHub = new Vue(); // use a Vue instance as event hub
To emit events you would use in any component:
eventHub.$emit('myevent', 'some value');
And, to listen to that event, again, in any component, do:
eventHub.$on('myevent', (e) => {
console.log('myevent received', e)
});
Demo:
var eventHub = new Vue(); // use a Vue instance as event hub
Vue.component('parent', {
template: "#parent-tpl",
created() {
eventHub.$on('event', (e) => {
console.log('event received at parent! value:', e);
});
}
});
Vue.component('inbetween', {
template: "#inbetween-tpl"
});
Vue.component('child', {
template: "#child-tpl",
methods: {
emitEvent() {
eventHub.$emit('event', 123);
}
}
});
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="parent-tpl">
<div>
<inbetween></inbetween>
</div>
</template>
<template id="inbetween-tpl">
<div>
<child></child>
</div>
</template>
<template id="child-tpl">
<div>
<h1>I'm the child</h1>
<button #click="emitEvent">Trigger EVENT</button>
</div>
</template>
<div id="app">
<p>{{ message }}</p>
<parent></parent>
</div>
Note: If creating a dedicated instance as event hub is something complicated in your environment, you can replace eventHub with this.$root (inside your components) and use your own Vue instance as hub.

Related

Emited event does't handled by parent component in Vue 2

for some reason emited event doesn't handles by parent component
HTML:
<template id="parent-template">
<div>
<h1>Parent: {{message}}</h1>
<child-component message="Child message"></child-component>
</div>
</template>
<template id="child-template">
<div>
<h2>Child: {{message}}</h2>
<button v-on:click="changeMessage('Changed')">Change</button>
</div>
</template>
<div id="app">
<parent-component message="Parent message"></parent-component>
</div>
JS (es5):
Child:
Vue.component("child-component", {
template: "#child-template",
props:['message'],
methods:{
changeMessage: function(newMessage){
this.message = newMessage;
this.$emit("message-changed", newMessage);
}
}
});
Parent:
Vue.component("parent-component", {
template: "#parent-template",
props:['message'],
mounted: function(){
var v = this;
this.on("message-changed", function(newValue){
alert("Emit handled!");
v.message = newValue;
});
}
});
So, everythings looks fine, but nothing happens when event fires. Why?
You cannot check for the emitted event on a mounted function, since the child Vue instances are not instantiated at that point. If you want to run the code AFTER everything has been rendered which is what I am assuming you are after then you need to run the code after a tick.
this.$nextTick(function () { // Your code goes here }
Also, for clarity, I would normally do a v-on:message-changed="parentMethod()" inside of the HTML. That way the parent is not tightly coupled to the child component at the mounted.
<child-component v-on:message-changed="parentMethod()"> </child-component>
Below is the Vue Documentation regarding the mounted information I provided:
https://v2.vuejs.org/v2/api/#mounted

Communicate between two components(not related with child-parent)

component 1
getMyProfile(){
this.$root.$emit('event');
console.log("emited")
},
component 2
mounted() {
this.$root.$on('event', () = {
alert("Fired");
}
}
I am trying to alert "fired" of comonent 2 from component 1. But this is not happening. what i am doing wrong. Should i have to add something on main js?
Other than the small typo in your $on, it's not clear what you're doing wrong, as you haven't provided enough context, but here's a working example of exactly what you're trying to do (send and receive an event via the $root element, without instantiating a separate eventbus Vue instance). (Many people do prefer to do the message passing via a separate instance, but it's functionally similar to do it on $root.)
I included a payload object to demonstrate passing information along with the event.
Vue.component('sender', {
template: '<span><button #click="send">Sender</button></span>',
methods: {
send() {
console.log("Sending...")
this.$root.$emit('eventName', {
message: "This is a message object sent with the event"
})
}
}
})
Vue.component('receiver', {
template: '<span>Receiver component {{message}}</span>',
data() {return {message: ''}},
mounted() {
this.$root.$on('eventName', (payload) => {
console.log("Received message", payload)
this.message = payload.message
})
}
})
new Vue({
el: '#app'
});
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<div id="app">
<sender></sender>
<receiver></receiver>
</div>
Personally I don't tend to use this pattern much; I find it's better to handle cross-component communication from parent to child as props, and from child to parent as direct $emits (not on $root). When I find I'm needing sibling-to-sibling communication that's generally a sign that I've made some poor architecture choices, or that my app has grown large enough that I should switch over to vuex. Piling all the event messaging into a single location, whether that's $root or an eventbus instance, tends to make the app harder to reason about and debug.
At the very least you should be very explicit in naming your events, so it's easier to tell where they're coming from; event names such as "handleClick" or just "event" quickly become mysterious unknowns.
So what you are looking for is an event bus (global events)
I'd advise considering using vuex anytime you have the need to implement an event bus.
Let's get back to the problem.
Create a file event-bus.js this is what's going to be capturing and distributing events.
import Vue from 'vue'
const EventBus = new Vue()
export default EventBus
Now in your main.js register your event bus
import Vue from 'vue'
import eventBus from './event-bus'
//other imports
Vue.prototype.$eventBus = eventBus
new Vue({
...
}).$mount('#app')
Now you can:
listen for events with this.$eventBus.$on(eventName)
emit events this.$eventBus.$emit(eventName)
in this example i'll bring event from child to parent component with $emit
Child Component:
Vue.component('Child component ', {
methods: {
getMyProfile: function() {
this.$emit('me-event', 'YUP!')
}
},
template: `
<button v-on:click="getMyProfile">
Emmit Event to Parrent
</button>
`
})
Parent Component:
Vue.component('Parent component ', {
methods: {
getEventFromChild: function(event) {
alert(event)
}
}
template: `
<div>
<Child-component v-on:me-event="getEventFromChild" />
</div>
`
})
for example when you have data flow one way from parent to child and you want to bring data from child to parent you can use $emit and bring it from child.. and in the parent you must catch it with v-on directive. (sry form my english)
If component 2 is the parent of the component 1 you could do:
getMyProfile(){
this.$emit('myevent');
console.log("emited")
},
for componant 2 componant like
<componant-2 #myevent="dosomething" ...></componant-2>
and in componant two
methods: {
dosomething() {
console.log('Event Received');
}
}

Using $emit to call a function

I'm new to vue.js and am trying to understand how $emit can be used. In the following code, every input element except for the last one works as expected.
My assumption in the last input element is that calling $emit with an event name would be the same as calling the function which has that event name, but it doesn't call that function. What is occurring with this $emit?
I've read through Binding Native Events to Components and that shows that it is to be used differently as I'm doing it. All this approach started from me watching a YouTube video (7 Secret Patterns...), specifically at this time https://youtu.be/7lpemgMhi0k?t=21m57s where you can see that usage on the slide.
Here is the code in JSFiddle https://jsfiddle.net/sbtmfweq/
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="test-app">
<input v-model="text1" placeholder="edit me" #keyup.enter="submit"> {{ text1 }}
<br><br>
<input :value="text2" #input="inputEvent" #keyup.enter="submit"> {{ text2 }}
<br><br>
<input :value="text2" #input="inputEvent($event)" #keyup.enter="submit"> {{ text2 }}
<br><br>
<input :value="text2" #input="$emit('inputEvent', $event.target.value)"> {{ text2 }} | {{reversedText2}}
</div>
<script>
new Vue({
el: '#test-app',
data: {
text1: 'text1',
text2: 'text2',
},
methods: {
log: console.log,
submit: function(event) {
console.log("submit -->", event, event.target.value, '<--')
},
inputEvent: function(event) {
console.log("input 2 -->", event.target.value, '<--')
this.text2 = event.target.value
},
},
watch: {
text1: {
handler: function(newValue, oldValue) {
console.log("input 1 -->", newValue, '<-->', oldValue, '<--')
}
}
},
computed: {
reversedText2: function () {
return this.text2.toUpperCase()
}
}
})
</script>
It's just a simple issue. Whenever you are dispatching an event, you have to add listener to it. In $emit, the first param is actually the name of the event.
As written in docs for $emit.
vm.$emit( eventName, […args] )
In the below code, I have added an event listener, using $on. Also I have changed the arg for $emit.
<input :value="text2" #input="$emit('inputEvent', $event)"> {{ text2 }} | {{reversedText2}}
<script>
new Vue({
...
created(){
this.$on('inputEvent', this.inputEvent);
}
...
})
</script>
I hope it helps.
$emit works like this.
Say you have 2 components, <parent> and <child>, where <child> is inside <parent>'s template.
When <child> emits an event, it can be handled by the <parent> component only (unless you register a listener via $on, but that's unrelated to your scenario). In your code, the $emit call is executed within the scope of the parent component (which is the root component instantiated with new Vue()); that is, the parent component is the one that's emitting the event. The root component has no parent so the emit call is pointless.
My assumption in the last input element is that calling $emit with an event name would be the same as calling the function which has that event name, but it doesn't call that function.
This isn't true; the name of the event has no relation to the method registered as a listener for that event in the parent.
it's normal that $emit doesn't work in your case.
In fact, $emit is used for communication between parent and child components. Here, you are using $emit but you haven't defined any parent component.
Here is the docs for the custom events.
Hope it helps!

Which Vue handler is called?

If one function is bound to multiple Vue event hooks, is there any way to know which event triggered the function?
Here's a contrived example:
<div v-on:click="handler" v-on:mouseover="handler">
Which event called handler? Click or Mouseover?
</div>
This could also help understand the context for Transition hooks and determining which was triggered.
<transition appear v-on:appear="handler" v-on:enter="handler">
From inside handler, can we tell which hook was the caller?
A practical workaround for these situations would be decorating common functionality behind two simple wrapper functions, but it seems like Vue probably has a way of understanding its event calling context which might have been overlooked.
The event object's type will tell you which event triggered the handler. You can get the event object in Vue using the $event variable:
Vue.component('demo', {
template: '<div v-on:click="handler($event)" v-on:mouseover="handler($event)">Click or hover on me</div>',
methods: {
handler(e) {
console.log(e.type); // e is the event object passed in as $event
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<demo></demo>
</div>

Can't stop Vue JS (v2) from reusing components

I have a Vue app that conditionally displays sets of the same component, I'd like to tie in to the created or mounted methods, but Vue keeps re-using earlier components and not triggering the methods. I've tried wrapping the components in keep-alive tags to no effect.
The code below shows what you would expect on the page: changing someVariable to 'test1' or 'test2' shows the relevant components, but the created/mounted methods only fire a maximum of 2 times (e.g. changing someVariable to 'test1' logs creation and mounting of 1 component with the label type1, then changing it to 'test2' only logs 1 more component with the label type3).
Currently using v2.1.10
HTML
<div id="app">
<div v-if="someVariable === 'test1'">
<keep-alive>
<test-component data-type="type1"></test-component>
</keep-alive>
</div>
<div v-if="someVariable === 'test2'">
<keep-alive>
<test-component data-type="type2"></test-component>
</keep-alive>
<keep-alive>
<test-component data-type="type3"></test-component>
</keep-alive>
</div>
</div>
JS/Vue
Vue.component('test-component', {
props: ['data-type'],
template: '<div><p>In the component: {{ dataType }}</p></div>',
created: function () {
console.log('component created: ', this.dataType);
},
mounted: function () {
console.log('component mounted: ', this.dataType);
}
});
var app = new Vue({
el: '#app',
data: {
someVariable: ""
}
});
You should use a watcher on your someVariable instead of trying to hook on created and mounted hooks.
Components are created, and mounted the first time they are visible (rendered). There are NO "shown" or "hidden" hooks.
See https://v2.vuejs.org/v2/guide/computed.html#Watchers:
watch: {
someVariable: function (newValue) {
// test newValue and do what you have to do!
}
}
For your specific example removing keep-alive from the second if should do the trick https://jsfiddle.net/z11fe07p/464/
An interesting thing is that vue seems to re-use the previously rendered component. So if you have one component in the first if when switching to the next if with 2 components it will re-use one component and create a new one for the second component from the block. When getting back to the first if block it will re-use one of the 2 already rendered components.
As mentioned above, a watcher is more suited for such cases, thus getting you rid of handling logic in places where you don't have full control. You can see this tip right here in the docs https://v2.vuejs.org/v2/api/#updated