v-model and child components?

vue.jsVuejs2

vue.js Problem Overview


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?

vue.js Solutions


Solution 1 - vue.js

As stated in the documentation,

v-model is syntactic sugar for:

> 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 - vue.js

Specify a :value prop and an @input event in 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>

Solution 3 - vue.js

use sync in your main instance and if you are use vue > 2.2 your need use emit into the component.

Check this doc:

A simple example (with vue 2.5):

Vue.component('my-input', {
	template: '<input v-on:keyup="onChange($event)" :value="field"></div>',
	props: ["field"],
	methods: {
		onChange: function (event) {
			this.$emit('update:field', event.target.value);
		}
	}
});

var vm = new Vue({
	el: '#app',
	data:{val: ''},
});

h1 span { color: red }

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<div id='app'>
 <h1>
   value
   <span>{{ val }}</span>
 </h1>
	<my-input :field.sync="val">
   </my-input>
 </div>

Solution 4 - vue.js

Solution for Vue 2

You can forward all attributes and listeners (including v-model) from parent to child like so:

<input v-bind="$attrs" v-on="$listeners" />

Here is the documentation for $attrs:

> Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn't have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind=" $attrs" - useful when creating higher-order components.

Make sure to set inheritAttrs to false to avoid having attributes applied to the root element (by default, all attributes are applied to the root).

Here is the documentation for $listeners:

> Contains parent-scope v-on event listeners (without .native modifiers). This can be passed down to an inner component via v-on="$listeners" - useful when creating transparent wrapper components.

Because v-model is just a shorthand for v-bind+v-on, it is forwarded as well.

Note that this technique is available since Vue 2.4.0 (July 2017), where this feature is described as "Easier creation of wrapper components".

Solution for Vue 3

Vue 3 removed the $listeners object because the listeners are now in the $attrs object as well. So you only need to do this:

<input v-bind="$attrs" />

Here is the documentation for $attrs:

> Contains parent-scope attribute bindings and events that are not recognized (and extracted) as component props or custom events. When a component doesn't have any declared props or custom events, this essentially contains all parent-scope bindings, and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.

If your component has a single root element (Vue 3 allows multiple roots elements), then setting inheritAttrs to false is still required to avoid having attributes applied to the root element.

Here is the documentation for inheritAttrs

> By default, parent scope attribute bindings that are not recognized as props will "fallthrough". This means that when we have a single-root component, these bindings will be applied to the root element of the child component as normal HTML attributes. When authoring a component that wraps a target element or another component, this may not always be the desired behavior. By setting inheritAttrs to false, this default behavior can be disabled. The attributes are available via the $attrs instance property and can be explicitly bound to a non-root element using v-bind.

Another difference with Vue 2 is that the $attrs object now includes class and style.

Here is a snippet from "Disabling Attribute Inheritance":

> By setting the inheritAttrs option to false, you can control to apply to other elements attributes to use the component's $attrs property, which includes all attributes not included to component props and emits properties (e.g., class, style, v-on listeners, etc.).

Solution 5 - vue.js

Example below shows you how to set the model from parent to child component and synchronize data between them. This is very useful when you split app forms to different components and use them in different contexts. This way you can use, for example, form fragments (components) in different places without repeating yourself.

PARENT COMPONENT

<template lang="pug">

  .parent
    Child(:model="model")
    br

    
    label(for="c") Set "c" from parent  
    input(id="c", v-model="model.c")

    .result.
      <br>
      <span> View from parent :</span>
      <br>
      a = {{ model.a }} 
      <br>
      b = {{ model.b }}
      <br>
      c = {{ model.c }}


</template>

<script>

import Child from './components/child.vue'

export default {

name: "App",

components: {
  Child
  },

  data() {
    return {
      // This model is set as a property for the child
      model: {
        a: 0,
        b: 0,
        c: 0
      }
    }
  },




};
</script>

CHILD COMPONENT

<template lang="pug">
  
  .child
    label(for="a") Set "a" from child  
    input(id="a", v-model="internalModel.a", @input="emitModel")
    <br>
    <br>

    label(for="b") Set "b" from child  
    input(id="b", v-model="internalModel.b", @input="emitModel")

    .result
      <br>
      span View from child
      <br>
      | a = {{ internalModel.a }} 
      <br>
      | b = {{ internalModel.b }}
      <br>
      | c = {{ internalModel.c }}

</template>

<script>


export default {

  name: 'Child',
  props: {
    model: {
      type: Object
    }
  },

  data() {
    return {
      internalModel: {
        a:0,
        b:0,
        c:0
      }
    }
  },

  methods: {
    emitModel() {
      this.$emit('input', this.internalModel)
    }
  },
  mounted() {
    this.internalModel = this.model;
  }

}
</script>

Solution 6 - vue.js

Binding data to a custom checkbox or checkbox set is quite different from binding it to a text input:

https://www.smashingmagazine.com/2017/08/creating-custom-inputs-vue-js/

<template>
  <label>
    <input type="checkbox" :checked="shouldBeChecked" :value="value" @change="updateInput">
    {{ label }}
  </label>
</template>
<script>
export default {
  model: {
    prop: 'modelValue',
    event: 'change',
  },
  props: {
    value: {
      type: String,
    },
    modelValue: {
      default: false,
    },
    label: {
      type: String,
      required: true,
    },
    // We set `true-value` and `false-value` to the default true and false so
    // we can always use them instead of checking whether or not they are set.
    // Also can use camelCase here, but hyphen-separating the attribute name
    // when using the component will still work
    trueValue: {
      default: true,
    },
    falseValue: {
      default: false,
    }
  },
  computed: {
    shouldBeChecked() {
      if (this.modelValue instanceof <span class="hljs-built_in">Array) {
        return this.modelValue.includes(this.value);
      }
      // Note that `true-value` and `false-value` are camelCase in the JS
      return this.modelValue === this.trueValue;
    }
  },
  methods: {
    updateInput(event) {
      let isChecked = event.target.checked;

      if (this.modelValue instanceof Array) {
        let newValue = [...this.modelValue];

        if (isChecked) {
          newValue.push(this.value);
        } else {
          newValue.splice(newValue.indexOf(this.value), 1);
        }

        this.$emit('change', newValue);
      } else {
        this.$emit('change', isChecked ? this.trueValue : this.falseValue);
      }
    }
  }
}
</script>

Solution 7 - vue.js

Using the following you can pass on all input attributes like placeholder:

Vue.component('my-input', {
  template: `<div>
    <input v-bind="$attrs" :value="value" @input="$emit('input', $event.target.value)">
    </div>`,
  inheritAttrs: false,
  props: ["value"],
})
new Vue({
  el: '#app',
  data: () => ({
    name: "",
  }),
})

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
  <div>Name: {{name}}</div>
  <input placeholder="Standard Input" v-model="name">
  <my-input placeholder="My Input" v-model="name"></my-input>
</div>

Solution 8 - vue.js

For Vue 3

The value prop mentioned in the accepted answer has become modelValue, and the emit event has been modified accordingly as well:

https://v3.vuejs.org/guide/migration/v-model.html#migration-strategy

^ Got it working by implementing the accepted answer with the few changes suggested in the migration strategy.

Solution 9 - vue.js

In addition to the above method, there is a simpler implementation

parent component

const value = ref('');

// provide value
provive('value', value);

child component

// inject value
const value = inject('value');

<input v-modelValue="value" />

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpanthroView Question on Stackoverflow
Solution 1 - vue.jsthanksdView Answer on Stackoverflow
Solution 2 - vue.jsCameron WilbyView Answer on Stackoverflow
Solution 3 - vue.jsfitorecView Answer on Stackoverflow
Solution 4 - vue.jsBenoit BlanchonView Answer on Stackoverflow
Solution 5 - vue.jsPawel KwiecienView Answer on Stackoverflow
Solution 6 - vue.jsgvlasovView Answer on Stackoverflow
Solution 7 - vue.jsSothView Answer on Stackoverflow
Solution 8 - vue.jsr89nView Answer on Stackoverflow
Solution 9 - vue.jsmidoushitongtongView Answer on Stackoverflow