6

How to binding parent's model to child in Vue.js?

These codes below is works fine. if i fill the input manually, then child's model return it's value to the parent's model.

But the issue is, if the data set from AJAX request in a parent, the input doesn't automatically filled.

Can anyone help me on this?

Form.vue

<template>
    <form-input v-model="o.name" :fieldModel="o.name" @listenChanges="o.name = $event"/>
    <form-input v-model="o.address" :fieldModel="o.address" @listenChanges="o.address = $event"/>
</template>

<script>
    import FormInput from '../share/FormInput.vue'

    export default {
        data () {
            return {
                o: {
                    name: '',
                    address: ''
                }
            }
        },
        components: { 'form-input': FormInput },
        created: function() {
            axios.get('http://api.example.com')
                .then(response => { 
                    this.o.name = response.data.name
                    this.o.address = response.data.address
                })
                .catch(e => { console.log(e) })
        }
    }
</script>

FormInput.vue

<template>
    <input type="text" v-model='fieldModelValue' @input="forceUpper($event, fieldModel)">
</template>

<script>
    export default {
        props: ['fieldModel'],
        data() {
            return {
                fieldModelValue: ''
            }
        },
        mounted: function() {
            this.fieldModelValue = this.fieldModel;
        },
        methods: {
            forceUpper(e, m) {
                const start = e.target.selectionStart;
                e.target.value = e.target.value.toUpperCase();
                this.fieldModelValue = e.target.value.toUpperCase();
                this.$emit('listenChanges', this.fieldModelValue)
            }
        }
    }
</script>
stkertix
  • 81
  • 1
  • 10

2 Answers2

4

Things are more straightforward if you take advantage of v-model in components.

If you put v-model on a component, the component should take a prop named value, and should emit input events to trigger it to update.

I like to make a computed to hide the event emitting, and allow me to just v-model the computed inside my component.

new Vue({
  el: '#app',
  data: {
    o: {
      name: '',
      address: ''
    }
  },
  components: {
    'form-input': {
      template: '#form-input',
      props: ['value'],
      computed: {
        fieldModelValue: {
          get() {
            return this.value;
          },
          set(newValue) {
            this.$emit('input', newValue.toUpperCase());
          }
        }
      }
    }
  },
  // Simulate axios call
  created: function() {
    setTimeout(() => {
      this.o.name = 'the name';
      this.o.address = 'and address';
    }, 500);
  }
});
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  Name ({{o.name}})
  <form-input v-model="o.name"></form-input>
  Address ({{o.address}})
  <form-input v-model="o.address"></form-input>
</div>

<template id="form-input">
    <input type="text" v-model='fieldModelValue'>
</template>
tony19
  • 125,647
  • 18
  • 229
  • 307
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Thanks a lot, your explanations helped me to get rid of [this Vue warning](https://stackoverflow.com/questions/39868963/vue-2-mutating-props-vue-warn) without loosing data sync, finally ! – AymDev Jun 30 '19 at 13:42
0

The mounted() hook is blocking subsequent updates from the parent.

Remove mounted and change v-model to 'fieldModel'

<template>
    <input type="text" :value='fieldModel' @input="forceUpper($event, fieldModel)">
</template>

<script>
  export default {
    props: ['fieldModel'],
    data() {
      return {
          fieldModelValue: ''
      }
    },
    // mounted: function() {
    //   this.fieldModelValue = this.fieldModel;
    // },
    methods: {
      forceUpper(e, m) {
        const start = e.target.selectionStart;
        e.target.value = e.target.value.toUpperCase();
        this.fieldModelValue = e.target.value.toUpperCase();
        this.$emit('listenChanges', this.fieldModelValue)
      }
    }
  }
</script>

Demo CodeSandbox

Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • 1
    Don't use `v-model`, use `value`. `v-model` is two-way, and you want one-way binding. – Roy J Jan 24 '18 at 22:06
  • If you are handling the return-value, v-model is not, so it is not doing two-way binding. – Roy J Jan 24 '18 at 22:13
  • I'm commenting on your code here, specifically this line: `v-model='fieldModel' @input="forceUpper($event, fieldModel)"` It should be `:value` rather than `v-model`, because you're handling the `input` events separately. – Roy J Jan 25 '18 at 00:04
  • both of your answer is hit the goals. btw, RichardMatsen answer give me warning says **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.**. so i choose Roy answer. – stkertix Jan 25 '18 at 00:45
  • Fair enough, he has the more comprehensive answer. – Richard Matsen Jan 25 '18 at 00:47
  • Your specific fix changed the code to use `v-model` on a prop, which is a bad idea. A 5 second update would get rid of bad code in your answer. – Roy J Jan 25 '18 at 01:08