The purpose of this series is to post tips & tricks about advanced Vue concepts that can be quickly applied to every application and give you a new weapon to approach problems.
In this short article, I will explain how v-model
works and how it can be applied to every Vue component.
Understanding v-model
v-model
is a common directive used in almost every Vue application. It's typically used to enable two-way data binding on form elements and works perfectly with input
, checkbox
, select
, textarea
and radio
.
In below example, v-model
applied on the input
element binds someVal
variable with native value
property of the input.
<input v-model="someVal">
Then the directive listens for native input
event and updates someVal
every time it's emitted.
So it turns out that we can rewrite the above code to well-known events and props with the same effect:
<input
v-bind:value="someVal"
v-on:input="someVal = $event.target.value"
>
This is how v-model
applied to regular input works under the hood.
Knowing this we can use v-model
on every component that will emit input
event and accept a value
prop.
Take a look at this MagicCounter
:
<template>
<div>
<button @click="changeValue(value-1)">-</button>
<span>{{ value }}</span>
<button @click="changeValue(value+1)">+</button>
</div>
</template>
<script>
export default {
props: ["value"],
methods: {
changeValue(newVal) {
this.$emit("input", newVal);
}
}
};
</script>
Since we are emitting input
event with a new value each time it's changed and accepting the value
prop we can safely use v-model
directive on this component:
<MagicCounter v-model="count" />
Using v-model with custom components
Event thought input
and value
pair is the default setup for v-model
depending on the input type, those bindings can be different (I strongly suggest checking it's source code for details). For example in checkbox
element checked
property and change
event are used instead of default ones.
It turns out that we customize the event/prop pair accepted by v-model
directive through a model
property. For example, this is how it could look like for checkbox
element:
model: {
prop: 'checked',
event: 'change'
}
You might want to change the name of the event emitted by our MagicCounter
to be more descriptive (for example modified
).
Let’s see how we can make this custom event work with v-model
<template>
<div>
<button @click="changeValue(value-1)">-</button>
<span>{{ value }}</span>
<button @click="changeValue(value+1)">+</button>
</div>
</template>
<script>
export default {
props: ["value"],
model: {
event: `modified`
},
methods: {
changeValue(newVal) {
this.$emit("modified", newVal);
}
}
};
</script>
..and voilà! Now you know how to use v-model
with every Vue component. I hope you'll find a way to use this knowledge very soon .
Here you can find a working example with a code from the post to play with.
Stay tuned for the next parts of the series!
Top comments (6)
I remember this topic was a hard one to grasp when I first started learning Vue.
The nice parts of
v-model
come in when you have to deal with custom radio / checkbox components because you have to put more effort in order to get the expected behavior. At least this is what I've encountered.Thank you for sharing!
You explanatory rewrite of the
v-model
directive using thev-bind
andv-on
directives was an absolute lightbulb moment. Understanding this was, for me, the key to moving forward and this explanation is fantastic.I had never come across the
model
property before, so I definitely also learned something here.Thanks for putting this together!
Thank you for sharing! Useful!
I try to create a radio / checkbox components with no success.
How to deal with v-model and custom radio / checkbox ? It seem to be not so easy.
Try to create some realistic component like an input for typing fraction value (denominator and numerator)
Everyone, in every tutorial is making a custom shit component of the element.
Huh
The codepen link solved an issue I had been trying to solve for at least an hour. Using model: is a lifesaver!
Thank you soooo much. You explained it the best.