v-model and child components?

I have a form and bind an input using v-model:

<input type="text" name="name" v-model="form.name">

Now I want to extract the input and make it it's own component, how do you then bind the values of the child component to the parents object form.name?


Solution 1:

As stated in the documentation,

v-model is syntactic sugar for:

<input
 v-bind:value="something"
 v-on:input="something = $event.target.value">

To implement the v-model directive for a custom component:

  • specify a value prop for the component
  • make a computed property with a computed setter for the inner value (since you should not modify the value of a prop from within a component)
  • define a get method for the computed property which returns the value prop's value
  • define a set method for the computed property which emits an input event with the updated value whenever the property changes

Here's a simple example:

Vue.component('my-input', {
  template: `
    <div>
      My Input:
      <input v-model="inputVal">
    </div>
  `,
  props: ['value'],
  computed: {
    inputVal: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit('input', val);
      }
    }
  }
})

new Vue({
  el: '#app',
  data() {
    return { 
      foo: 'bar' 
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
  <!-- using v-model... -->
  <my-input v-model="foo"></my-input>
  
  <!-- is the same as this... -->  
  <my-input :value="foo" @input="foo = $event"></my-input>

  {{ foo }}
</div>

Thanks to @kthornbloom for spotting an issue with the previous implementation.

Breaking changes in Vue 3

Per the documentation, there are breaking changes to the v-model implementation in Vue 3:

  • value -> modelValue
  • input -> update:modelValue

Solution 2:

Specify a :value prop and an @input event on the child component, then you can use v-model syntax in the parent component.

Vue 2

MyInput.vue

<template>
  <input 
    :value="value" 
    @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  props: ['value']
};
</script>

Screen.vue

<template>
  <my-input v-model="name" />
</template>

<script>
import MyInput from './MyInput.vue';

export default {
  components: { MyInput },

  data: () => ({
    name: ''
  })
};
</script>

Vue 3

MyInput.vue

<template>
  <input 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)" />
</template>

<script>
export default {
  props: ['modelValue']
};
</script>

Screen.vue

<template>
  <my-input v-model="name" />
</template>

<script>
import MyInput from './MyInput.vue';

export default {
  components: { MyInput },

  data: () => ({
    name: ''
  })
};
</script>