Vue.js - How to emit to parent component on change in data object? - vue.js

The use of v-model creates a two-way bind between the view and data model WITHIN a component. And a view interaction can emit an event to a parent component.
It is possible, though, to have a data model change in a child component emit an event to a parent component?
Because as long as the user is the one clicking the checkbox, things are fine in both the parent and the child (the data model updates AND the event is emitted). But if I pull up the Vue dev tools and toggle the checkbox within the child component's data, the two-way bind from the v-model will make the appropriate updates within the CHILD component, but nothing ever makes it over to the parent component (I suspect because an event is not emitted).
How can I make sure the parent component "knows" about the data change in the child component? I assume this must be possible in some way... If not emitting from the child, perhaps there's some way to have the parent component "watch" the child component's data?
Thank you for any help or guidance! I'll keep reading and looking for an answer in the meantime!
child component
<template>
<div class="list-item" v-on:click="doSomething">
<input type="checkbox" v-model="checked">
<label v-bind:class="{ checked: checked }">{{ name }}</label>
</div>
...
</template>
<script>
...
data: function() {
return {
checked: false,
}
},
methods: {
doSomething() {
...
this.$emit('doSomething', this)
}
}
</script>
parent component
<template>
<ChildComponent v-on:doSomething="getItDone"></ChildComponent>
...
</template>
<script>
...
methods: {
getItDone(target) {
...
}
}
</script>
UPDATE: After playing around a bit more with #IVO GELOV's solution, the issue I'm running into now is that, when multiple Child components are involved, since the Parent's one singular value of myBooleanVar drives the whole thing, checking the box of one child component causes all child components to be checked.
So it's definitely progress in that both view and data manipulations make it over to the parent, but I'm still trying to figure out how to "isolate" the situation so that just the one Child component that was acted upon gets dragged into the party...

You can keep the data in the parent, provide it to the child as a prop, make the child watch the prop and update its internal state, and finally emit an event to the parent when the internal state of the child has been changed.
parent
<template>
<ChildComponent v-model="myBooleanVar" />
...
</template>
<script>
data()
{
return {
myBooleanVar: false,
}
},
watch:
{
myBooleanVar(newValue, oldValue)
{
if (newValue !== oldValue) this.getItDone(newValue);
}
},
methods:
{
getItDone(value)
{
...
}
}
</script>
child
<template>
<div class="list-item">
<input type="checkbox" v-model="checked">
<label :class="{ checked: checked }">{{ name }}</label>
</div>
...
</template>
<script>
props:
{
value:
{
type: Boolean,
default: false
}
}
data()
{
return {
checked: this.value,
}
},
watch:
{
value(newVal, oldVal)
{
// this check is mandatory to prevent endless cycle
if(newVal !== oldVal) this.checked = newVal;
},
checked(newVal, oldVal)
{
// this check is mandatory to prevent endless cycle
if(newVal !== oldVal) this.$emit('input', newVal);
}
},
</script>

Maybe you can directly bind the checkbox attribut to the parent attribut using v-bind="$attrs"
Take a look at this answer: https://stackoverflow.com/a/56226236/10514369

Related

How to add class to the parent component when the button is clicked in child in vue

I have a button in my child component and when I click this button I want to add a class in my parent component. I added child component as a slot in parent component.
parent component:
<template>
<div :class="editMode ? 'class-add' : ''">
<slot name="default"></slot>
</div>
</template>
<script>
export default {
props: {
editMode: {
type: Boolean,
required: true,
},
},
};
</script>
child component:
<button #click="addClass">Click Me!!!</button>
addClass() {
this.$emit('edit-abc', true);
},
And here how I am adding the class:
<parent-component :edit-mode="editMode">
<template #default>
<child-component #edit-abc="editAbc($event)" />
</template>
</parent-component>
The problem is as you see, I have several abcs (abcs is an object which includes several abc) to send to child the class only the one which is clicked. So I believe here #edit-abc="editMode = $event", instead of editMode = $event, I need to create a function and filter the one that I want to add the class but my logic is wrong somewhere. Here what I have done as a function.
editAbc(event) {
this.abcs.filter((a) => {
if (a.id) {
this.$nextTick(() => {
return (this.editMode = event);
});
}
});
},
You have to declare the editMode data property to use it in your event handling.
data() {
return {
editMode: false
};
}
If you need to send separate events, then simply use different events.
You intentions with "several abcs" are not really clear. And it looks for me like you have a design flaw.
Please clarify it further.
UPDATE
Here is a stackblitz with the solution.

Vue how to get value from sub component

Here is my use case:
My main page have several sub-components that collect different input from user, finally I want to submit the whole page with all inputs collected. Therefore I want to retrieve the data from sub-component
One option is to use store, but my sub-components are super simple, just some forms, store seems too heavy...
Another option is that I can modify prop, although I know this is bad practice, but this approach looks just perfect....
Is it ok to modify prop if my logic is simple?(just collect inputs from user)Or I have to go for Vuex and store
Expanding on excellent answers from Ifaruki and Andres Foronda, another, related option is the use of the sync modifier on the child component's prop.
Suppose the child component has a prop named name. In the parent component, you can use the sync modifier like this:
<Child :name.sync="childName"></Child>
Then, in the child component, when the value of the name prop should be updated, don't update it directly. Instead, emit an event that follows the naming convention for sync-able props, which is update:nameOfProp. So in our example, the child component would do this:
this.$emit('update:name', newName);
The benefit of the sync modifier is that we don't have to write an event handler function in the parent component--Vue does that for us and updates the variable that is bound to the prop automatically.
You can read more details about the sync modifier in the official docs.
Retreiving data from sub component works with $emit here an exapmle:
//parent copmonent
<template>
<div>
<child #someEvent="someMethod"></child>
</div>
</template>
import child from "path/"
<script>
export default {
components: {
child
},
methods: {
someMethod(data){
console.log(data);
}
}
}
</script>
Child component
<template>
<div>
<button #click="sendEvent">send</button>
</div>
</template>
<script>
export default {
methods: {
sendEvent(){
this.$emit("someEvent", "working");
}
}
}
</script>
$emit takes 2 arguments. The first is the event name and the second one is the data that you send.
The parent just needs to listen with # for that event that being fired.
you can listen an event from child an update the parent data property
//parent component
<div>
<input-name #updateName="eventToUpdateName" /> <!--child component-->
</div>
...
data: () => ({ nameFromChild: '' )},
methods: {
eventToUpdateName(value) {
this.nameFromChild = value; // Update from child value emitted
}
}
...
And in the child component
// Child component
<input v-model="name" />
...
data: () => ({ name: '' }),
// watch for changes in the name property and emit an event, and pass the value to the parent
watch: { name() { this.$emit('updateName', this.name } }
...
Also, You can use a v-model directive and emit 'input' event from child.
//parent component
<div>
<input-name v-model="nameFromChild" /> <!--child component-->
</div>
...
data: () => ({ nameFromChild: '' )}
...
Now in the child component you can have
// Child component
<div>
<input v-model="name" />
</div>
data: () => ({ name: '' }),
props: { value: { type: String, default: '' },
created() { this.name = this.value }, // You can receive a default value
watch: { name() { this.$emit('input', this.name } }
...

Change value of a props in beforeDestroy/Destroyed cycle of child component does not work

Hi I made a boolean value in parent component, and passed it to the child component as a props. it has initialized as false, and after the user view the component, the value will change to true, which means the page has been visited.
I have done some research and followed How to properly pass data from child to parent and parent to child component?
here is my js code:
<script>
export default {
props: {
hasLoad: {
type: Boolean
}
},
data () {
return {
hasLoadModel: this.hasLoad
}
},
created: function() {
console.log(this.hasLoad);
},
beforeDestroy: function() {
this.hasLoadModel = true;
this.hasLoad = true;
console.log(this.hasLoadModel);
console.log(this.hasLoad);
}
}
</script>
and html code
<div v-model="skillLoadModel">..</div>
But I still get
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value.
I have tried to change the value at either of beforeDestroy or Destroyed, or do not use v-model, but none of them works. The value has changed after I left the page, but when I reenter the page, the value has reset to default value.
Can someone help me please?
Thanks
Don't change the value of the prop. Have the component emit an event so that the parent can take the appropriate action.
Below is an example of a component that is created when the checkbox is checked, and is destroyed when it gets unchecked. The component emits a "dying" event, and the parent receives it and prints a scream to the console.
new Vue({
el: '#app',
data: {
showIt: true
},
methods: {
scream() {
console.log("Aaarg!");
}
},
components: {
myComponent: {
beforeDestroy: function() {
this.$emit('dying');
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<input type="checkbox" v-model="showIt">
<my-component v-if="showIt" hasload="true" #dying="scream" inline-template>
<div>Here I am</div>
</my-component>
</div>
I assume you're trying to communicate to the parent that the child has loaded. In that case, you can pass a function as a prop and simply call it when the child mounts.
Parent HTML:
<child :my-load-fn="loadFn"></child>
Parent JS:
methods: {
loadFn() {
this.childHasLoaded = true
}
}
Child JS:
props: ['myLoadFn'],
mounted() {
this.myLoadFn()
}

How to close dialog by self with vuejs2?

I am writing a dialog component, i don't know how to close dialog by self.
<template>
<div class="dialog" v-show="visible">
...
<button #click="close">Close</button>
</div>
</template>
<script>
{
props: {visible: {type: Boolean, default: false}},
methods: {
close () {
// this.visible = false //It will get vue warn
}
}
}
</script>
So, how to close the dialog in my component, i can't update visible prop, i will get a error.
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible"
Props are one way data flow
You should not mutate the prop received from a parent component in the child component
So emit an custom event to mutate the prop in parent component itself
<template>
<div class="dialog" v-show="visible">
...
<button #click="close">Close</button>
</div>
</template>
<script>
{
props: {visible: {type: Boolean, default: false}},
methods: {
close () {
// this.visible = false //It will get vue warn
this.$emit('close-dialog')
}
}
}
</script>
parent component
<template>
<div>
<my-dialozg #close-dialog="visible = false" :visible="visible"><my-dialog>
</div>
</template>
Setup an event listener close-dialog on the dialog component and set the data property visible that you pass as a prop to false. You can do this inline as shown above or extract it into a method also
In vuejs, child component can not modify the parents property directly.
You can use events/event listeners for that. But since your example is simple you don't need an event for that.
Demo: https://jsfiddle.net/samayo/943bx5px/28/
Instead, you can pass the prop visible to your component as :visisble="visible" and watch the state change as:
watch: {
visible (val) {
if(!val) {
// close
}else{
open
}
}
}
Now, if visisble is toggled to false from the parent, your modal will not be visible.

2-way binding in Vue 2.3 component

I understand the .sync modifier returned in Vue 2.3, and am using it for a simple child component which implements a 'multiple-choice' question and answer. The parent component calls the child like this:
<question
:stem="What is your favourite colour?"
:options="['Blue', 'No, wait, aaaaargh!']
:answer.sync="userChoice"
>
The parent has a string data element userChoice to store the result from the child component. The child presents the question and radio buttons for the options. The essential bits of the child look like this (I'm using Quasar, hence q-radio):
<template>
<div>
<h5>{{stem}}</h5>
<div class="option" v-for="opt in options">
<label >
<q-radio v-model="option" :val="opt.val" #input="handleInput"></q-radio>
{{opt.text}}
</label>
</div>
</div>
</template>
export default {
props: {
stem: String,
options: Array,
answer: String
},
data: () => ({
option: null
}),
methods: {
handleInput () {
this.$emit('update:answer', this.option)
}
}
}
This is all working fine, apart from the fact that if the parent then changes the value of userChoice due to something else happening in the app, the child doesn't update the radio buttons. I had to include this watch in the child:
watch: {
answer () {
this.option = this.answer
}
}
But it feels a little redundant, and I was worried that emitting the event to update the parent's data would in fact cause the child 'watch' event to also fire. In this case it would have no effect other than wasting a few cycles, but if it was logging or counting anything, that would be a false positive...
Maybe that is the correct solution for true 2-way binding (i.e. dynamic Parent → Child, as well as Child → Parent). Did I miss something about how to connect the 'in' and 'out' data on both sides?
In case you're wondering, the most common case of the parent wanting to change 'userChoice' would be in response to a 'Clear Answers' button which would set userChoice back to an empty string. That should have the effect of 'unsetting' all the radio buttons.
Your construction had some oddities that didn't work, but basically answer.sync works if you propagate it down to the q-radio component where the changing happens. Changing the answer in the parent is handled properly, but to clear values, it seems you need to set it to an object rather than null (I think this is because it needs to be assignable).
Update
Your setup of options is a notable thing that didn't work.
I use answer in the q-radio to control its checked state (v-model has special behavior in a radio, which is why I use value in conjunction with v-model). From your comment, it looks like q-radio wants to have a value it can set. You ought to be able to do that with a computed based on answer, which you would use instead of your option data item: the get returns answer, and the set does the emit. I have updated my snippet to use the val prop for q-radio plus the computed I describe. The proxyAnswer emits an update event, which is what the .sync modifier wants. I also implemented q-radio using a proxy computed, but that's just to get the behavior that should already be baked-into your q-radio.
(What I describe is effectively what you're doing with a data item and a watcher, but a computed is a nicer way to encapsulate that).
new Vue({
el: '#app',
data: {
userChoice: null,
options: ['Blue', 'No, wait, aaaaargh!'].map(v => ({
value: v,
text: v
}))
},
components: {
question: {
props: {
stem: String,
options: Array,
answer: String
},
computed: {
proxyAnswer: {
get() {
return this.answer;
},
set(newValue) {
this.$emit('update:answer', newValue);
}
}
},
components: {
qRadio: {
props: ['value', 'val'],
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
}
}
}
}
},
methods: {
clearSelection() {
this.userChoice = {};
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="app">
<question stem="What is your favourite colour?" :options="options" :answer.sync="userChoice" inline-template>
<div>
<h5>{{stem}}</h5>
<div class="option" v-for="opt in options">
<div>Answer={{answer && answer.text}}, option={{opt.text}}</div>
<label>
<q-radio :val="opt" v-model="proxyAnswer" inline-template>
<input type="radio" :value="val" v-model="proxyValue">
</q-radio>
{{opt.text}}
</label>
</div>
</div>
</question>
<button #click="clearSelection">Clear</button>
</div>