Emit listener gets removed from $attrs when event is declared in emits option - vue.js

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.

Related

can not get element by getElementById in beforeDestroy lifecycle vue

I use document.getElementById in beforeDestroy lifecycle to get an element. But getElementById returns null. If I use ref, I can get the element. Is there any difference between them? Why document.getElementById can't get the element?
There is an issue with the timing and there could be many reasons for that.
But in your case , The ref was still in the virtual DOM object, which will be destroyed within the destroy event (Unlike the document template).
That would be one reason Vue recommends to use $refs if possible.
I had the same problem. I thought the beforeDestroy hook was the right place to kill event listeners before leaving the component. For my surprise, when the route changes, the component is already unmounted when this hook is called.
This is vue.js version 2, I don’t know if it is better in version 3.

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.

Binding and custom event triggering on multiple level in VUE.JS

I am new in VUE.JS but i have finished some beginner courses of vuemastery. Though I know how to bind properties and how to emit custom events, I have a problem: I don't know how to make these things through multiple levels of components.
Let's say we have the following hierarchy:
I need to have control on the Home component's properties from the Elements and Input components from the bottom of the diagram. Right now I am emitting custom events from level to level from down to up, but it doesn't look like an elegant solution.
Is there a better way to do this? And of course when I change one of the properties from Input component it need to have effect on the properties in the Element components as well.
For example the Element components are elements having width and height calculated based on totalWidth property, which can be edited in the Input component. I'm having here a warn as well in the console: [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
Use this.$root.$emit to emit event on the root component, that would then propagate event on its child components, regardless of depth level
I would recommend using vuex to manage the state of your application. Emitting events all over the place is not the most elegant solution.

Try to understand Vue2's $emit

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!

Vue.js custom event naming

I have two components, one contains another.
And when I trigger event from child I can't receive it in parent.
Child component
this.$emit('myCustomEvent', this.data);
Parent component
<parent-component v-on:myCustomEvent="doSomething"></parent-component>
But, when I changed event name to my-custom-event in both places it works.
Vue somehow transform event names? Or what can be a problem?
I read docs about component naming convention but there nothing related to event naming
It is recommended to always use kebab-case for the naming of custom events. Lower case events, all smashed together, as recommended by #KoriJohnRoys would also work but are harder to read. It is not recommended to use camelCase for event naming.
The official documentation of Vue.JS states the following under the topic of Event Names:
Event Names
Unlike components and props, event names don’t provide any automatic case transformation. Instead, the name of an emitted event must exactly match the name used to listen to that event. For example, if emitting a camelCased event name:
this.$emit('myEvent')
Listening to the kebab-cased version will have no effect:
<my-component v-on:my-event="doSomething"></my-component>
Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event names.
In addition to #ssc-hrep3's point on kebab-case
The docs for .sync recommend using the pattern update:myPropName
For custom events, the safest option is to just use a lower-cased event name all smashed together. Currently even kebab-case can have issues.
this.$emit('mycustomevent', this.data);
then, in the parent component, feel free to bind to a camel-cased function
<parent-component v-on:mycustomevent="doSomething"></parent-component>
it's a bit janky, but it works.
Source (states that kebab-case doesn't work either)
Vue.js transforms not only xml tags (component names) but attributes as well, so when you are generating event
$emit('iLikeThis')
you must handle it as:
v-on:i-like-this="doSomething"
From docs:
When registering components (or props), you can use kebab-case,
camelCase, or TitleCase.
...
Within HTML templates though, you have to use the kebab-case
equivalents: