event name in child component that passes data to parent component - vue.js

In a Vue Native project I have been passing eventName as a prop from the parent component to the child component and then having the child component emit this eventName back to the parent component with whatever data it is providing to the parent. Here is an example with the pertinent sections of code shown:
parent: settings.vue
// code in template
<dynamic-picker
#event-setting1="(value) => setting1 = value"
:eventName="'event-setting1'"
:value="setting1"
:choices="config.picker.setting1.choices"
/>
<dynamic-picker
#event-setting2="(value) => setting2 = value"
:eventName="'event-setting2'"
:value="setting2"
:choices="config.picker.setting2.choices"
/>
<dynamic-picker
#event-setting3="(value) => setting3 = value"
:eventName="'event-setting3'"
:value="setting3"
:choices="config.picker.setting3.choices"
/>
// code in script
import DynamicPicker from '../../../components/dynamic-picker.vue';
export default {
components: {
DynamicPicker
}
}
child: dynamic-picker.vue
// code in template
<dynamic-picker
:items="choices"
:selected="selected"
:onValueChange="(value) => $emit(eventName, value)"
/>
// code in script
props: ['value', 'eventName', 'choices']
However, it seems to work if the child has a static event name, in which case the code changes to this:
parent: settings.vue
// code in template
<dynamic-picker
#event-setting="(value) => setting1 = value"
:value="setting1"
:choices="config.picker.setting1.choices"
/>
<dynamic-picker
#event-setting="(value) => setting2 = value"
:value="setting2"
:choices="config.picker.setting2.choices"
/>
<dynamic-picker
#event-setting="(value) => setting3 = value"
:value="setting3"
:choices="config.picker.setting3.choices"
/>
child: dynamic-picker.vue
// code in template
<dynamic-picker
:items="choices"
:selected="selected"
:onValueChange="(value) => $emit('event-setting', value)"
/>
// code in script
props: ['value', 'choices']
While I like this second approach better than the first because it is simpler, I don't understand why it works without causing issues. How can the child component be called multiple times in the parent file and emit the same event name no matter whether setting1, setting2 or setting3 is calling it without causing problems? I search the Vue docs but did not see this addressed.
Edit:
Two clarifications -
The dynamic-picker tag that appears in dynamic-picker.vue is imported as follows into that file:
import DynamicPicker from './dynamic-picker.js';
Added code above that appears in script tags for settings.vue.

If I understand your question correctly, each dynamic-picker tag maps to its own unique instance of a dynamic-picker component. As such, there is a one to one mapping from a given dynamic-picker to its parent, and only that one parent will hear an emit from the child.
For example, the first tag in the parent corresponds with child instance one, the second tag in the parent corresponds with child instance two, etc. Now, only the first tag is the parent of instance one. So, only the first tag will respond to an emit from instance one.

Related

How to pass all events to far ancestor component in vuejs?

The question is similar to this one, except, the emit event is not going to the grand parent, but a further one.
How to pass all events to parent in VueJS
The way I am trying to emit all events up the stack is this way:
<View_5 /> <!-- does an emit event -->
<View_4 v-on="$attrs" /> <!-- pass all events to parent -->
<View_3 v-on="$attrs" /> <!-- pass all events to parent. But it breaks here. -->
At View_3, it doesnt pass the events to its parents. What I'm i doing wrong?
[EDIT] - Here is a link to a sample project on stackblitz
Click the black square, and you can see the text changes. This works because it bubbled to the a "go" event from components D -> to C -> to B -> to A, using the old fashion way.
Now how do i make it so that components C and B do NOT specifically look for the "go" event, but simply pass all events up to component A?
Personally, I'm not a big fan of emitting the events up the stack if the event is not emitted to a direct parent and should go way up, exactly for the reasons you mentioned: it may be hard to follow where exactly things break. But that's just my opinion. What I do like to do in such cases is to use EventBus.
Essentially, an event bus is a Vue.js instance that can emit events in one component, and then listen and react to the emitted event in another component directly — without the help of a parent component.
First create an eventBus.js file (I like to store mine in a utils directory):
import Vue from 'vue'
const EventBus = new Vue()
export default EventBus
In your child component:
import EventBus from '#/utils/eventBus
export default {
//rest of your setup
methods: {
myMethodHandler() {
EventBus.$emit('myEvent')
}
}
}
And then in the grand parent components (the component that has to receive the event):
import EventBus from '#/utils/eventBus
export default {
//rest of your setup
created() {
EventBus.$on('myEvent', () => {
// your business logic here
})
}
}
Of course you can give the events whatever name that you like and then listen to the same event. And you can pass payload if needed - just pass it in the emitted event right after the event name and receive them in the EventBus callback function:
EventBus.$emit('myEvent', someString, someObject)
//...
EventBus.$on('myEvent', (someStringPayload, someObjectPayload) => {
// do your thing
})
The examples above are for Vue2. For Vue3, according to the official doc, you can use a third party library, such as mitt or tiny-emitter.
v-on="$attrs" should be v-bind="$attrs".
$attrs contains key-value pairs of attributes and their values. For #go="handler", $attrs would be { onGo: handler }, where the on-prefix is automatically to the key.
v-on="obj" creates event handlers for the key-value pairs in obj. For instance, v-on="{ foo: handler } creates a listener for the foo event that runs handler().
Given the above, v-on="$attrs" in your case would incorrectly create a listener for the onGo event (when it really should be for the go event). Further, each v-on="$attr" in the nested components would prepend on to the name at each nested level, leading to onOnOnGo in DD.vue.
Solution
Use v-bind="$attrs" to correctly forward the v-on directive:
<!-- AA.vue -->
<BB #click="onClick" />
<!-- BB.vue -->
<CC v-bind="$attrs" />
<!-- CC.vue -->
<DD v-bind="$attrs" />
<!-- DD.vue -->
<button v-bind="$attrs" />
demo

VueJs - What's The Correct Way to Create a Child Component With Input Fields

I'm trying to use vuejs to display a list of instances of a child component.
The child component has input fields that a user will fill in.
The parent will retrieve the array of data to fill in the child components (If any exists), but since they're input fields the child component will be making changes to the value (Which generates errors in the console, as child components aren't supposed to change values passed from the parent).
I could just be lazy and just do everything at the parent level (i.e. use a v-for over the retrieved array and construct the list of elements and inputs directly in the parent and not use a child component at all), but I understand that it's not really the vuejs way.
I'm not very familiar with child components, but I think if it was just static data I could just declare props in the child component, and fill it from the parent.
However what I kind to need to do is fill the child component from the parent, but also allow changes from within the child component.
Could someone please describe the correct way to achieve this?
Thanks
You can use inputs on child components. The pattern is like this (edit it's the same pattern for an array of strings or an array of objects that each have a string property as shown here):
data: function() {
return {
objects: [ { someString: '' }, { someString: '' } ]
}
}
<the-child-component v-for="(object, i) in objects" :key="i"
v-model="object.someString"
></the-child-component>
Then, in the child component:
<template>
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
/>
</div>
</template>
export default {
name: 'the-child-component',
props: ['value'],
}
See it described here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components

Get v-if expression/data as plain text in child component

Hy there,
I try to create a custom Vue component which is shown based on v-if directive. I also want to change the directive data (modalStatus) value from inside the component.
<modal v-if="modalStatus"></modal>
To update the data from the component i use a method similar to this.
closeModal () {
this.$parent.modalStatus = false
}
The problem is that sometimes i don't know the name of the data model (modalStatus) , can be anything.
My question is how can i get the data/expression name as a plain text from the modal component ?
I'm planing to use something like this to update the modalStatus
this.$parent['anyName'] = false
Thanks and stay safe !
Later Edit. I know how to accomplish all of the above using props or v-model. I wonder if is possible using strictly v-if. Thanks!
There are several approaches to get to a method or property in the parent component from the child.
The 'Vue Way' is to emit a message telling the parent to close.
Send the name in as a property
Parent
<child modalName='modalStatus' />
Child
this.$parent[this.modalName]=false
Send in a method
Parent
<child :close='onClose' />
// component method
onClose(){
this.modalStatus=false
}
Child
this.close()
Emit a message
Parent
<child-component #close='modalStatus=false' />
// or call a method
<child-component #close='onClose' />
// component method
onClose(){
this.modalStatus=false
}
Child
this.$emit('close')

Can't copy props to model data and render it in Vue 2

I'm having this problem that looks a lot like a bug to me and I can't figure out how to solve it.
I created a generic list component and I tell it what child component it should insert in each item and what are the data it should pass to the child component. I'm passing everything as props along with the list (array) itself.
The problem is that I can't mutate the list props. So I try to copy it to model attribute. Otherwise I get this error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders.....
And I can't just make it work in any of the lifecycle events. When I save the file and the hot-reloading reloads the page, the list is there, rendered, full of items. When I press F5 to manually reload the page, it is no more. Everything seems to be alright with code though
So in the parent component I'm doing this:
<List ref="link_list"
:list="this.foo.links" //this is array
:child="'LinkFormItem'" //this is the name of the child component
:section_name="'Links'"
:defaults="{content: '', type: 'facebook'}" />
In the List component I get this:
Template
<li class="" v-for="item in datalist">
<component :is="child" :item="item" ></component>
<button v-on:click='remove(index++)' type="button" name="button" class='red button postfix small'>Remove</button>
</li>
Script
<script>
import Child1 from './Child1'
import Child2 from './Child2'
export default {
name: 'search',
props: ['child', 'list', 'defaults','section_name'], //it is received as 'list'
components: {
Child1, Child2
},
data () {
return {
index: 0,
datalist: [] //i'm trying to copy 'list' to 'datalist'
}
},
beforeMount: function () {
// i'm copying it
for(var k in this.list){
this.datalist.push(this.list[k])
}
},
methods: {
//and here I should change it the way I want
add: function () {
this.datalist.push(this.defaults)
},
getList () {
return this.datalist;
},
remove(index){
var datalist = [];
for(var k in this.datalist){
if(k != index) datalist.push(this.datalist[k]);
}
this.datalist = datalist;
}
}
}
</script>
I don't see any problems with my Script. What is going on??
#edit
Ok, some console.log later I found out what the problem seems to be. The HTTP Request is really taking much longer than the mounting of the component to happen. But when it happens, it is not triggering the update in the list component. Nothing is re-rendered and the list is empty.
Workaround
well I realised the problem was related to propagation. I made a few changes in the code to asure the parent component was updating and changing the model value. but the child component (the list component) was not receiving it.
then I gave up trying to understand why and did the following:
1- used the ref in the child component to force an update in the child component with $forceUpdate and then I was assigning the props to the model in the beforeUpdate event. It was causing an error: an re-rendering loop. The update caused a new update and so on. We could just use a flag to stop it.
2- Instead I just called a child method directly:
this.$refs.link_list.updateList(data.links);
I hate this approach because I think it's way too explicit. But it did the job. Then in the child component a new method:
updateList(list){
this.datalist = list;
}
3- The other possibility that passed through my mind was emitting an event. But I didn't try, too complicated
You can simply do like as follows
data () {
return {
index: 0,
datalist: this.list // to copy props to internal component data
}
},
Once you done about you need to apply data manipulation opertions on new this.datalist , not on this.list
If you don't want to mutate the original list array you can do this:
data () {
return {
index: 0,
datalist: Object.assign({}, this.list)
}
}
I think this will help you

How can I get a child component's watcher's updated value from parent component in Vue 2?

I have two components that are in a parent-child relationship. In the child select component I've defined a watcher for the currently selected value and I want the parent to get the updated value whenever it changes. I tried to use events but the parent can only listen to its own vm.
In the child component
watch: {
selected (value) {
this.$emit('selectedYearChange', value)
}
}
In the parent
mounted () {
this.$on('selectedYearChange', function (value) {
this.selectedYear = value
})
}
This obviously doesn't work because of the reason I mentioned above.
I wasn't able to find relevant examples in the documentation.
You can setup an event listener on the child component element itself where its used in the parent and pass it a method to be executed
//parent component
<div id="app>
<child-comp #selected-year-change='changeYear'></child-comp>
</div>
In your parent component use the method changeYear to change the value of `selectedYear'
methods:{
//you get the event as the parameter
changeYear(value){
this.selectedYear = value
}
}
See the fiddle
And avoid using camelCase for event names use kebab-case instead as HTML attributes are case-insensitive and may cause problems
html:
<Parent>
<Child #selected-year-change=HandleChange(data)></Child>
</Parent>
Javascript/Child Component:
watch: {
selected (value) {
this.$emit('selectedYearChange', value)
}
}
Javascript/Parent Component:
HandleChange: function(data){}
good luck. maybe work!