3

I'm trying to use a combination of v-for and v-model to get a two-way data bind for some input forms. I want to dynamically create child components. Currently, I don't see the child component update the parent's data object.

My template looks like this

<div class="container" id="app">
  <div class="row">
    Parent Val
    {{ ranges }}
  </div>

   <div class="row">
     <button 
        v-on:click="addRange"
        type="button" 
        class="btn btn-outline-secondary">Add time-range
     </button>
    </div>

  <time-range 
    v-for="range in ranges"
    :box-index="$index"
    v-bind:data.sync="range">
  </time-range>

</div>

<template id="time-range">
  <div class="row">
    <input v-model="data" type="text">
  </div>
</template>

and the js this

Vue.component('time-range', {
  template: '#time-range',
  props: ['data'],
  data: {}
})

new Vue({
  el: '#app',
  data: {
    ranges: [],
  },
  methods: {
    addRange: function () {
        this.ranges.push('')
    },
  }
})

I've also made a js fiddle as well https://jsfiddle.net/8mdso9fj/96/

4
  • you can emit custom events to send data from child to parent in vue2. have you tried that ? Commented Sep 4, 2017 at 22:34
  • In particular, you are trying to use the .sync modifier. You should read the documentation for how that works. Commented Sep 4, 2017 at 23:13
  • Hi guys. I did try the .sync modifier but didn't have any luck with it jsfiddle.net/8mdso9fj/97 Commented Sep 4, 2017 at 23:53
  • v-model on a prop is not going to work. You need to set it up to emit an update:data event. Commented Sep 5, 2017 at 0:47

1 Answer 1

4

Note: the use of an array complicates things: you cannot modify an alias (the v-for variable).

One approach that isn't mentioned often is to catch the native input event as it bubbles up to the component. This can be a little simpler than having to propagate Vue events up the chain, as long as you know there's an element issuing a native input or change event somewhere in your component. I'm using change for this example, so you won't see it happen until you leave the field. Due to the array issue, I have to use splice to have Vue notice the change to an element.

Vue.component('time-range', {
  template: '#time-range',
  props: ['data']
})

new Vue({
  el: '#app',
  data: {
    ranges: [],
  },
  methods: {
    addRange: function () {
        this.ranges.push('')
    },
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
  <div class="row">
    Parent Val
    {{ ranges }}
  </div>
  
   <div class="row">
     <button 
        v-on:click="addRange"
        type="button" 
        class="btn btn-outline-secondary">Add time-range
     </button>
    </div>

  <time-range 
    v-for="range, index in ranges"
    :data="range"
    :key="index"
    @change.native="(event) => ranges.splice(index, 1, event.target.value)">
  </time-range>
</div>

<template id="time-range">
  <div class="row">
    <input :value="data" type="text">
  </div>
</template>

To use the .sync modifier, the child component has to emit an update:variablename event that the parent will catch and do its magic. In this case, variablename is data. You still have to use the array-subscripting notation, because you still can't modify a v-for alias variable, but Vue is smart about .sync on the array element, so there's no messy splice.

Vue.component('time-range', {
  template: '#time-range',
  props: ['data'],
  methods: {
    emitUpdate(event) {
      this.$emit('update:data', event.target.value);
    }
  }
})

new Vue({
  el: '#app',
  data: {
    ranges: [],
  },
  methods: {
    addRange: function () {
        this.ranges.push('')
    },
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
  <div class="row">
    Parent Val
    {{ ranges }}
  </div>
  
   <div class="row">
     <button 
        v-on:click="addRange"
        type="button" 
        class="btn btn-outline-secondary">Add time-range
     </button>
    </div>

  <time-range 
    v-for="range, index in ranges"
    :data.sync="ranges[index]"
    :key="index">
  </time-range>

</div>

<template id="time-range">
  <div class="row">
    <input :value="data" type="text" @change="emitUpdate">
  </div>
</template>

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @RoyJ ! That did it for the input fields. I had some other custom fields I ran into some issues with but I got around it by using watch and emitting events similar to the regular text inputs

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.