Try to understand Vue2's $emit - vue.js

I wonder how Vue2's $emit works?
On its API(https://v2.vuejs.org/v2/api/#vm-emit), it says:
Trigger an event on the current instance. Any additional arguments
will be passed into the listener’s callback function.
If current instance means the component defined, while its main usage is to send signal to parent(https://v2.vuejs.org/v2/guide/components.html#Sending-Messages-to-Parents-with-Events)
I wonder how to understand this(the way how the event passed is pretty confused to me)?
THanks

The wording on the docs might create confusion to some people.
Maybe something in the lines of: "Makes the vm current instance dispatch an event" would be clearer?
In the end it is just a classic pub/sub pattern: your component instance emits / dispatches / fires an event, and other components (typically the parent one) listen / subscribe (v-on / #) to that event.

Simply put, $emit register an event in a component, and then you can listen to this event in other places wherever the component is used.
Say if you have a Child.vue and somewhere in this component, you did:
.$emit('some-event')
Then you can listen to this event when Child component is reused, for instance in another component SomeComponent.vue, you can do:
<template>
<child #some-event="doSomething"></child>
</template>
So here the event is triggered in the child component, but you decide what to do in the parent component with doSomething. Hope this makes sense!

Related

Emit listener gets removed from $attrs when event is declared in emits option

Vue 3 introduced the option to declare a components emitted events in the emits option, while also removing the $listeners attribute in favor of the $attrs.
In $attrs event listeners for events get added an prefixed with on so an event called click is accessed in the component through $attrs.onClick. But when the click events is declared in the emits option, it dissapears from $attrs.
This is made by design in Vue 3, since the $attrs attribute only is meant to include things not declared in the component.
If you want to access the components event listeners, while having them declared, you can change them to props and propagate the listening function into the component that was like mentioned here.
There's also a discussion on the Vue.js core Github about this behaviour here.

Use $emit without a click event

I'm new to Vue and there are some things I didn't understand yet.
In the first child there's a click event to $emit information to the next child. But in the other one, the second child, is there anyway to pass the information to the next child using $emit without a click event or any other event (like scroll, hover etc)? I just want to pass it ahead. Can I use mounted()?
Yes! What you're looking for is $emit(), in combination with the v-on directive, both of which it sounds like you're already using.
In the child component that you mention, the lowest one on the chain, you're already capturing a click event from some component, and then presumably using $emit('my-event', [...some data...]) to emit a new event out from the child component itself.
All you need to do now is to add event listeners and handlers in the components up the chain, using Vue's custom events mechanic, so that they can receive and emit their own events, and the data can make it up to the parent. Custom events allow your components to emit and listen for events of any name, which may carry any data they'd like (this means they're not limited to being click/ hover/ blur/ etc.).
Here's the step-by-step of that approach:
(Structure: Parent <--> Child #1 <--> Child #2 <--> Child #3)
Child #3 captures a click event, and emits an event in its handler
Child #2 is listening for this event on #3, captures it, and $emits its own event in its handler
Child #1 is listening for this event on #2, captures it, and emits own event in its handler
Parent captures event, and executes handler
In code, you'll have a listener and handler in each of your components, which can look something like this:
(I've used inline handlers here for brevity, but you can use defined methods as handlers too, just call this.$emit(...) from the method.)
Child #3:
<component ... #click="$emit('my-event-a', $event)" />
Child #2:
<ChildComponent3 ... #my-event-a="$emit('my-event-b', $event)" />
Child #1:
<ChildComponent2 ... #my-event-b="$emit('my-event-c', $event)" />
Parent:
<ChildComponent1 ... #my-event-c="myHandler" />
$event here is just special Vue syntax for referencing the event data in an inline handler, which in this case allows your new event to contain the same data.
The events in the various child components can be named whatever you'd like, just make sure that the respective listeners are listening for the correct event name (eg. #event-x="..." for $emit('event-x') and #pizza-eaten="..." for $emit('pizza-eaten')).
I also wanted to mention that passing the event object (or any data at all) with an emitted event is completely optional. To do so, just invoke $emit with the event name and no other arguments: $emit('my-event'). This is useful when you simply care that an event has occurred, and the handler requires no further info.
There are many ways to approach this problem, but this is the most straightforward to understand, and works great as long as you don't need tons of direct interaction between a component and its deeply nested children.
If you do find your component structure getting more complicated, then you may want to look into a fully realized state management solution, like Vuex, which allows your components to interact with a state manager rather than having to deal with tons of events through a complicated component structure.

Is there a way to be notified when event listeners are added to a Vue component?

I have a custom Vue component that does not render to HTML.
When the component is first mounted, I am able to loop through this.$listeners and optimize the underlying non-HTML event implementation accordingly (e.g. not to emit mousemove type events unless something is listening).
To complete this process I'd like to know when listeners are programatically added later through $on().
Is there any non-polling way to be notified of this? My current workaround is to listen for and emit everything but that is a poor solution.
Note that in some instances I want to be able to use the events in question, and others not. For example:
<custom-component #eventThatWouldFireOften="doSomething" #anotherEvent="doMoreStuff"/>
and another usage might be simply.
<custom-component ref="custom"/>
...
mounted() {
this.$refs.custom.$on('anotherEvent', ...)
}
So in the first case the result would be:
CustomComponent tells the underlying API it wants to listen for eventThatWouldFireOften and anotherEvent
CustomComponent receives eventThatWouldFireOften and anotherEvent events from the underlying API and re-emits them as Vue events that can be listened to using v-on or # syntax.
..and the second case the result would be:
CustomComponent tells underlying API it doesn't want to listen for anything just yet
When the parent of CustomComponent is mounted it programatically listens for anotherEvent. That needs to be communicated down to the base API (what I'm trying to solve).

Child to parent data Vue JS

Hi Guys, I'm not quite sure how to do this, I'm very new to Vue, how can I send my data from NewDeal component to DealsTable component? As you can see NewDeal has two parent components. I've read about $emit and $on, but just want to confirm if do I really need to do it like newDeal -> QuickActions -> SideBar? And I am not sure how to go from there if what I'm thinking is correct. I hope you can shed some light on me.
I've tried emitting an event from NewDeal component and listening it from the DealsTable component but didn't work, I am not sure if this is possible, or it should really be that the child is a direct descendant of the parent?
Thanks!
Fixed it using a global variable bus and replacing this by bus.
Declaration in app.js:
window.bus = new Vue({})
Emitter:
bus.$emit()
Listener:
bus.$on()
I've tried emitting an event from NewDeal component and listening it from the DealsTable component but didn't work
Looks like you have a solution but I thought I'd comment on this one part in case it helps. The reason that DealsTable didn't see the event from NewDeal is that vue events emitted by a child component are only propagated to the immediate parent component and no higher.
If you want the vue event to be seen higher up the chain then the child's parent would need to emit the event once it listened for the event and received it. In this way each parent could bubble the event up the chain but in many situations that's too complicated, and so your window.bus approach is the way to go. Hope that help make what you were seeing make more sense.

Vue common component communicate with different parent

I have a requestion, how about vue.js's common component's best practice when communicating with different parent component.
for example, in my scenario, a basic modal component, trigger a 'close' method, but it has two different parent component
I find two solutions:
parent need pass an additional prop, and then baisc component just
trigger event which event's name is the prop value, so the listener
parent component attched on could be called
in basic modal just use this.$parent to visit parent component methods, or this.$parent.trigger('xxx'), and then parent knows what to do
But, both above I think not very good, the first may need pass an additional prop, this let others who write a third, a forth parent component use the basic component not very handy. And the second may be felt more hard coded.
So, is there is better solution in this case?
Use this.$dispatch('eventName', data) (for Vue 2.x, use this.$emit('eventName', data)), to trigger an event to any parent, grandparent and further up the chain (you can use this.$broadcast('eventName', data) to trigger events down the chain in Vue < 2.x).
If the parent has an event called 'eventName' then it will fire this event.
If you have multiple parents, you can give them each a different event and from the child fire this specific event via dispatch. You can also give each parent the same event and pass a data prop that specifies what the parent should do. Third option is to refer to the specific parent:
var parent = new Vue({ el: '#parent' })
// access child component instance
parent.$refs.eventName()
Each option has pros and cons. The best solution depends on the context. But I think that the best solution in general is option 1. Then you don't need an additional data parameter. Option 3 is not loosely coupled.
For more info: https://vuejs.org/guide/components.html