Are you ready to master two-way binding in Vue js 3 using defineModel
? In this comprehensive guide, we'll explore how to implement seamless data flow between components using this powerful composition API Macro. By the end, you'll have a clear understanding of how to use the classic v-model
directive and leverage defineModel
for efficient two-way binding in your Vue.js applications.
Two-way binding acts like a real-time bridge between a component's visual elements (the template) and its underlying data. This dynamic duo ensures that whenever a user interacts with the component, like clicking a button or entering text, the data is automatically updated behind the scenes.
And guess what? It works the other way around too! If you programmatically change the data in your Vue.js application, the component's template will instantly reflect the update. This seamless synchronization keeps your application reactive and responsive to user interactions, resulting in a unique user experience.
Imagine you have a parent component named App.vue
with a form where users can enter information. You want any edits they make to be instantly reflected in a variable called inputValue
. Likewise, if you update inputValue
in your code, the form should show the new value. This effortless back-and-forth exchange is the power of two-way binding! Let’s have a look at the following script setup with composition API example:
<template>
<div>
<form action="/">
<input v-model="inputValue" />
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
const inputValue = ref();
</script>
Using v-model with basic input fields is a piece of cake. But what if you want to create custom components with their own data and behaviors? This is where Single-File Components (SFCs) come in! Let's explore how to achieve two-way binding magic with SFCs.
Let's break down two-way binding with a simple example using v-model
. Imagine we have a parent component App.vue
with a custom component called FormInput
. This FormInput
component will display a text input field. We want any changes made in the input to be reflected in the parent component's data.
<template>
<div>
<FormInput v-model="inputValue" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import FormInput from './components/FormInput.vue';
const inputValue = ref('');
</script>
We define a variable ref called inputValue
using ref
to store the input field's value. Inside the FormInput
component, things get interesting!
Originally, the FormInput
component is just a basic text input:
<template>
<input type="text" />
</template>
But thanks to the v-model
magic on the parent, FormInput
receives a special prop named modelValue
. This prop holds the current value of inputValue
from the parent component. We can access this prop using defineProps
:
<template>
<input type="text" />
</template>
<script setup>
defineProps(['modelValue']);
</script>
We then bind the modelValue
to the input's value
attribute to display the current data:
<template>
<input type="text" :value="modelValue" />
</template>
<script setup>
defineProps(['modelValue']);
</script>
So far, this is a one-way street, as the parent tells the child what to show. To achieve two-way binding, we need to inform the parent about any changes in the input field. We do this by listening to the input
event and emitting an update event to the parent. But there's a twist! We need to emit an update specifically for modelValue
.
Here's how we achieve this:
<template>
<input type="text" @input="updateValue" :value="modelValue" />
</template>
<script setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
const updateValue = (e) => {
const value = e.target.value;
emit('update:modelValue', value);
};
</script>
We define an updateValue
function that captures the new value from the input event and emits an update:modelValue
event with the new data. Because v-model
on the child is expecting this specific event (update:modelValue
), it automatically updates the parent's inputValue
with the new value we emitted.
By following these steps, we create a two-way data flow between the parent and child component using v-model
. Any changes in the FormInput
will be reflected in the parent's data, keeping everything in sync!
While v-model
is convenient, it can become verbose for custom components with complex data structures. Here's where defineModel steps in, offering a more streamlined approach.
Here's how it looks in action (replacing v-model
with defineModel
):
<template>
<input type="text" v-model="modelValue" />
</template>
<script setup>
const modelValue = defineModel();
</script>
defineModel
takes care of both data synchronization and event emission behind the scenes, eliminating the need for manual event handling and keeping your code concise.
While v-model
remains useful for basic scenarios, defineModel
offers a cleaner and more concise approach for complex data structures in custom components. As you explore building advanced Vue.js applications, defineModel
becomes a valuable tool for efficient two-way data binding.
Feel free to have a look at the complete implementation code written for this article.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.