v-model and child components?
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>
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 thevalue
prop's value - define a
set
method for the computed property which emits aninput
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
use sync
in your main instance and if you are use vue > 2.2 your need use emit
into the component.
Check this doc: - https://alligator.io/vuejs/upgrading-vue-2.3/#propsync
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 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
andstyle
) 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 forclass
andstyle
), and can be passed down to an inner component viav-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 viav-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
tofalse
, 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 usingv-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 tofalse
, you can control to apply to other elements attributes to use the component's$attrs
property, which includes all attributes not included to componentprops
andemits
properties (e.g.,class
,style
,v-on
listeners, etc.).