Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuejs : How to pass an object as prop and have the component update sub-objects

Tags:

vue.js

vuejs2

I am trying to create a component that accepts an object as prop and can modify different properties of that object and return the value to the parent, using either sync or emit events. The example won't work, but it's simply to demonstrate what I'm trying to achieve.

Here's a snippet of what I'm trying to achieve :

Vue.component('child', {
  template: '#child',
  
  //The child has a prop named 'value'. v-model will automatically bind to this prop
  props: ['value'],
  methods: {
    updateValue: function (value) {
      //Not sure if both fields should call the same updateValue method that returns the complete object, or if they should be separate
      this.$emit('input', value);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    parentObject: {value1: "1st Value", value2: "2nd value"}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{parentObject}}</p>
  <child v-model="parentObject"></child>
</div>

<template id="child">
   <input type="text" v-bind:value.value1="value" v-on:input="updateValue($event.target.value)">
   <input type="text" v-bind:value.value2="value" v-on:input="updateValue($event.target.value)">
</template>
like image 508
Storm Avatar asked Mar 02 '18 15:03

Storm


People also ask

How do you pass an object as a prop in Vue?

To pass in the properties of an object as props, we can use the v-bind without the argument. Then the properties of post will be passed into blog-post as prop values. The property names are the prop names.

Do Props update Vue?

As long as you're updating a reactive property (props, computed props, and anything in data ), Vue knows to watch for when it changes. All we have to do is update count , and Vue detects this change. It then re-renders our app with the new value!

Can I pass function as Props Vue?

It's a pretty common question that newer Vue developers often ask. You can pass strings, arrays, numbers, and objects as props. But can you pass a function as a prop? While you can pass a function as a prop, this is almost always a bad idea.

How do you pass dynamic components to Props?

We can pass props to dynamic components using the v-bind directive. To display dynamic components, we add the component component with the is prop. currentComponent is a string with the component name. v-bind has an object with the props that we want to pass to whatever component is being rendered now.


2 Answers

You shouldn't modify the object being passed in as a prop. Instead, you should create a new data property in the child component and initialize it with a copy of the prop object.

Then, I would just use a v-model for each of the inputs in the child component and add a deep watcher to the internal value which would emit an update whenever the internal value changes.

Vue.component('child', {
  template: '#child',
  props: ['value'],
  data() {
    return { val: {...this.value} };
  },
  watch: {
    val: {
      deep: true,
      handler(value) {
        this.$emit('input', value);
      }
    }
  }
});

new Vue({
  el: '#app',
  data: {
    parentObject: {value1: "1st Value", value2: "2nd value"}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>

<div id="app">
  <p>Parent value: {{parentObject}}</p>
  <child v-model="parentObject"></child>
</div>

<template id="child">
  <div>
    <input type="text" v-model="val.value1">
    <input type="text" v-model="val.value2">
  </div>
</template>

I made a shallow copy of the prop in my example ({...this.value}), because the object doesn't have any nested properties. If that wasn't the case, you might need to do a deep copy (JSON.parse(JSON.stringify(this.value))).

like image 180
thanksd Avatar answered Oct 13 '22 13:10

thanksd


In #app, shoudld be parentObject, not parentValue.

In child, you had two inpyt, but you must have a single root element. In the example below I created a <div> root element for the component.

To update the parent, emit the events. This approach does not modify the parent's property in the child, so there's no breaking of the One-Way data flow.

Vue.component('child', {
  template: '#child',
  
  //The child has a prop named 'value'. v-model will automatically bind to this prop
  props: ['value']
});

new Vue({
  el: '#app',
  data: {
    parentObject: {value1: "1st Value", value2: "2nd value"}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{parentObject}}</p>
  <child v-model="parentObject"></child>
</div>

<template id="child">
<div>
   <input type="text" v-bind:value="value.value1" v-on:input="$emit('input', {value1: $event.target.value, value2: value.value2})">
   <input type="text" v-bind:value="value.value2" v-on:input="$emit('input', {value1: value.value1, value2: $event.target.value})">
</div>
</template>

About the <input>s: you can bind each to a property of the parent's value. Then, when edited, emit an event modifying just that property (v-on:input="$emit('input', {value1: $event.target.value, value2: value.value2})) and keeping the other's value. The parent updates in consequence.

If you have many properties, you can replace in the second input, for example:

$emit('input', {value1: value.value1, value2: $event.target.value})

With

$emit('input', Object.assign({}, value, {value2: $event.target.value}))


Using a method instead of emitting directly from the template

I kept the previous demo because it is more direct and simpler to understand (less changes from your original code), but a more compact approach would be to use a method (e.g. updateValue) and reuse it in the template:

Vue.component('child', {
  template: '#child',
  
  //The child has a prop named 'value'. v-model will automatically bind to this prop
  props: ['value'],
  methods: {
    updateValue: function(propertyName, propertyValue) {
      this.$emit('input', Object.assign({}, this.value, {[propertyName]: propertyValue}))
    }
  }
});

new Vue({
  el: '#app',
  data: {
    parentObject: {value1: "1st Value", value2: "2nd value"}
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{parentObject}}</p>
  <child v-model="parentObject"></child>
</div>

<template id="child">
<div>
  <input type="text" v-bind:value="value.value1" v-on:input="updateValue('value1', $event.target.value)">
  <input type="text" v-bind:value="value.value2" v-on:input="updateValue('value2', $event.target.value)">
</div>
</template>

The demo above, as you can see, already uses Object.assign(), meaning it will handle an indefinite number of properties.

like image 45
acdcjunior Avatar answered Oct 13 '22 13:10

acdcjunior