Learn about the v-model directive in Vue.js, and get to know the basics of two-way binding and how you can use it in your own app development.
More often than not I get comments and messages asking me to go into detail about v-model
by people that have read an article or attended a workshop and the magic of v-model
is touched on but not thoroughly explained.
Today, we will go into detail on what exactly this directive does for us in Vue, and a top-level glance at how it works behind the scenes.
This article is intended for novice and intermediate users who want to further their understanding of the directive, and I am assuming a general basic knowledge of Vue as a whole.
Oftentimes we find ourselves describing the v-model
directive as a magical entity that allows creating a two-way binding to an input element. But what exactly does the two-way binding mean? And why should you care?
Vue and other frameworks like it have a bunch of magical methods and ways of doing things. v-model
is a great example of this type of thing.
The entry-level knowledge required to use it is minimal because, frankly, you don’t really NEED to understand how it works in order to use it - but when you fully grasp the concept behind it, the way you use it or think about it changes.
Let’s start with a simple input element, using type email.
<input type="email" />
The problem is simple: We need to be able to know what the user types in here. And we may need to send it to the back end for them to log the user in, or to capture it for a registration form.
How would you approach this using jQuery or vanilla JS?
In jQuery maybe you would add an id
attribute to the element, and target it directly to extract the value.
<input type="email" id="email" />
$('#email').val();
The problem with this approach is that you are stuck having to then add an event listener if you want to react to keystrokes, because so far you are getting a static value at the moment the code is executed. It is NOT reactive.
Let’s try this again with an event listener and vanilla JS.
const el = document.querySelector('#email');
el.addEventListener('input', function(event) {
// when the user types this will fire
const inputValue = event.target.value;
doSomethingWith(inputValue);
});
Alright, we’re getting somewhere! So far we are able to call the function doSomethingWith
with the event’s value (what the user typed). This seems like a lot of code though, and what happens if we have a form with 30 different inputs?
Let’s do it the Vue way. We are going to add an event listener to the input and call our fake doSomethingWith
function every time it fires.
<input type="email" @input="doSomethingWith" />
I don’t know about you, but this seems like magical avocado mischief to me. How does Vue accomplish the same thing behind the scenes?
First of all, notice that we don’t need an id
anymore. In fact, I would argue that using id
in Vue is a terrible idea!
If you use ids in Vue and you use the component in several places, then you are going to have several instances of an element with the same id - which spells out CHAOS.
Your developer avocado has gone bad, frand. GG. 🥑☠️
Let’s go back to our example though when we add @input
to our element. Vue is smart enough to attach the necessary event listener to this particular element via reference. It will also handle removing this event listener for us!
Finally, it will call the function that we passed inside the " "
whenever the event is fired, and it will pass it the event
object. Neat!
Let’s move on to problem #2.
You managed to listen to the events of the user making inputs on your field - good work! (Hopefully using Vue and not jQuery, come on. I am disappointed. ☹️)
Now, part two of “two-way binding”. What if we want to dynamically do something with the user’s email and have the input reflect the change?
Maybe we have some sort of form autocomplete, or validation, or we have another input element that will prepopulate their name from the database. There’s a lot of possible scenarios.
Let’s approach this problem with jQuery first. 🤢
// This is the value we are storing somewhere
// So that later we can send it to the backend
const userEmail = 'somevalue@theuserentered.com';
$('#email').on('input', function() {
userEmail = $('#email').val();
});
// Now what if we want to change the email on the input programmatically?
function changeEmail(newEmail) {
$('#email').val(newEmail);
userEmail = newEmail;
}
changeEmail('your-email@is-wrong.com');
You can see from this last example how quickly this can start to get really messy. Monolithic files of jQuery for event handling and input validation are a thing of the past!
You can also appreciate how it’s going to be a problem keeping a state
. We have a high-level variable userEmail
that is keeping the value, and we have to be careful that we are orderly about our code. Now do this 40 times for a big form, please.
One thing that you may have also not considered at this point is that we are trying to be really careful about setting the .val
of our input when we change it on the changeEmail
function. But what if another dev, or even ourselves, makes another function that modifies the userEmail
variable somewhere else?
We have to keep in mind that every time this variable changes the input has to be updated, or we have to get into some rather advanced JavaScript that will set up getters and setters for us to fix that reactivity problem.
Let’s approach this second problem in Vue. We are going to create first a local state in our make-believe component.
data() {
return {
email: ''
}
}
Now that we have our local state, we have to tell the input to use it and bind it to the value.
<input
type="email"
:value="email"
@input="doSomethingWith"
/>
methods: {
doSomethingWith(event) {
this.email = event.target.value;
// Do other stuff, eat avocados, play zelda and admire a raccoon
}
}
That’s it! Every time the email
state changes, the input will be updated accordingly. We now have two ways of binding to the input.
First, when our local state changes. Second, when the user types on the field, the input
listener will update the state
with the value. When the state updates, it will update the input.
Do you see the cycle? DO YA?
The kind folks at the Vue realized that this pattern of adding two one-way bindings, one that feeds into the input, and one that feeds out of the input was very common when dealing with forms and user data.
Thus, the magical avocado and the v-model
directive were born. Both were cared for and nurtured, and the magical avocado went bad during the night and we had to toss it out. But such is life.
What happens then when you have to two-way bind your inputs? Do you have to go through this double process where you bind the :input
to some sort of state, and then listen to an event and rewrite the whole state?
The answer is no! v-model
, your friendly neighborhood avocado, to the rescue.
We currently have this for our form input.
<input
type="email"
:value="email"
@input="doSomethingWith"
/>
data() {
return {
email: ''
}
},
methods: {
doSomethingWith(event) {
this.email = event.target.value;
// Do other stuff, eat avocados, play zelda and admire a raccoon
}
}
And with the power invested in me by Vue, and the blessing of Captain Planet (yes, I’m old), we can make it all nice and simple.
<input type="email" v-model="email" />
data() {
return {
email: ''
}
}
That’s it! v-model
will make sure that the correct event is being listened to (in the case of native elements like inputs, selects, etc.) and then bind our local email
data property to it! Ah-two, ah-way, ah-binding. 👌
Keep in mind, v-model
has some caveats regarding which property it has to bind to and which event it has to listen to.
Vue is super smart regarding this behind the scenes when it’s used on inputs, selects, checkboxes and radio buttons - but when you are working with custom components, you are going to have to do this heavy lifting yourself.
This, however, is out of the scope of this beginner article. But you can check out this reference on v-model
on custom components on the official documentation, or the last part of my Vue for Beginners Series where I touch on v-model
.
Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.