Vue 2, how to use v-model
By Flavio Copes
Learn how to use the Vue 2 v-model directive to create a two-way binding on form inputs, plus handy modifiers like .lazy, .trim, and .number for cleaner data.
The v-model Vue directive allows us to create a two-way binding. You put it on a form element, and Vue keeps the element and a data property in sync, in both directions.
You can bind a form input element for example, and make it change the Vue data property when the user changes the content of the field:
<input v-model="message" placeholder="Enter a message">
<p>Message is: {{ message }}</p>
Type in the field, and the paragraph updates as you type. Change message in your code, and the field updates too. That’s the two-way part.
It works on select elements as well:
<select v-model="selected">
<option disabled value="">Choose a fruit</option>
<option>Apple</option>
<option>Banana</option>
<option>Strawberry</option>
</select>
<span>Fruit chosen: {{ selected }}</span>
And on checkboxes, where a single checkbox binds to a boolean:
<input type="checkbox" v-model="subscribed">
What v-model does under the hood
v-model is syntax sugar. For a text input, this:
<input v-model="message">
is the same as writing:
<input :value="message" @input="message = $event.target.value">
Vue binds the value attribute to the data property, and updates the property on every input event. Knowing this helps when something behaves unexpectedly: there’s no magic, just a prop and an event.
Handy directive modifiers
To make the model update when the change event occurs, and not any time the user presses a key, you can use v-model.lazy instead of just v-model.
Working with input fields, v-model.trim is useful because it automatically removes whitespace.
And if you accept a number instead than a string, make sure you use v-model.number. Without it, an input always gives you a string, and age + 1 turns "30" into "301".
Nested properties
Say you have a shopping cart, and you have a component that holds a form to add a product:
<template>
<div class="">
<h1>Add Product</h1>
<label>Name</label>: <input>
<label>Description</label>: <textarea></textarea>
<button @click="addProduct">Add</button>
</div>
</template>
<script>
export default {
name: 'AddProduct',
data() {
return {
product: {
name: '',
description: ''
}
}
},
methods: {
addProduct() {
console.log(this.product)
}
}
}
</script>
To make the form update the inner properties of the product state value, you use product.*:
<label>Name</label>: <input v-model="product.name">
<label>Description</label>: <textarea v-model="product.description"></textarea>
Be careful here: declare every property you bind to in data, like name and description above. Vue 2 cannot detect properties added to an object after it’s created, so binding v-model to an undeclared property gives you a field that updates the object but never re-renders the page. Declaring the full shape upfront, even with empty strings, fixes it.