Component v-model
v-model
can be used on a component to implement a two-way binding.
First let's revisit how v-model
is used on a native element:
template
<input v-model="searchText" />
Under the hood, the template compiler expands v-model
to the more verbose equivalent for us. So the above code does the same as the following:
template
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
When used on a component, v-model
instead expands to this:
template
<CustomInput
:model-value="searchText"
@update:model-value="newValue => searchText = newValue"
/>
For this to actually work though, the <CustomInput>
component must do two things:
- Bind the
value
attribute of a native<input>
element to themodelValue
prop - When a native
input
event is triggered, emit anupdate:modelValue
custom event with the new value
Here's that in action:
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Now v-model
should work perfectly with this component:
template
<CustomInput v-model="searchText" />
Another way of implementing v-model
within this component is to use a writable computed
property with both a getter and a setter. The get
method should return the modelValue
property and the set
method should emit the corresponding event:
vue
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
v-model
arguments
By default, v-model
on a component uses modelValue
as the prop and update:modelValue
as the event. We can modify these names passing an argument to v-model
:
template
<MyComponent v-model:title="bookTitle" />
In this case, the child component should expect a title
prop and emit an update:title
event to update the parent value:
vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
Multiple v-model
bindings
By leveraging the ability to target a particular prop and event as we learned before with v-model
arguments, we can now create multiple v-model
bindings on a single component instance.
Each v-model
will sync to a different prop, without the need for extra options in the component:
template
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
vue
<script setup>
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
Handling v-model
modifiers
When we were learning about form input bindings, we saw that v-model
has built-in modifiers - .trim
, .number
and .lazy
. In some cases, you might also want the v-model
on your custom input component to support custom modifiers.
Let's create an example custom modifier, capitalize
, that capitalizes the first letter of the string provided by the v-model
binding:
template
<MyComponent v-model.capitalize="myText" />
Modifiers added to a component v-model
will be provided to the component via the modelModifiers
prop. In the below example, we have created a component that contains a modelModifiers
prop that defaults to an empty object:
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Notice the component's modelModifiers
prop contains capitalize
and its value is true
- due to it being set on the v-model
binding v-model.capitalize="myText"
.
Now that we have our prop set up, we can check the modelModifiers
object keys and write a handler to change the emitted value. In the code below we will capitalize the string whenever the <input />
element fires an input
event.
vue
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
Modifiers for v-model
with arguments
For v-model
bindings with both argument and modifiers, the generated prop name will be arg + "Modifiers"
. For example:
template
<MyComponent v-model:title.capitalize="myText">
The corresponding declarations should be:
js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }
Here's another example of using modifiers with multiple v-model
with different arguments:
template
<UserName
v-model:first-name.capitalize="first"
v-model:last-name.uppercase="last"
/>
vue
<script setup>
const props = defineProps({
firstName: String,
lastName: String,
firstNameModifiers: { default: () => ({}) },
lastNameModifiers: { default: () => ({}) }
})
defineEmits(['update:firstName', 'update:lastName'])
console.log(props.firstNameModifiers) // { capitalize: true }
console.log(props.lastNameModifiers) // { uppercase: true}
</script>