Understanding v-model and .sync for Two-Way Data Binding in Vue.js
In Vue.js, the v-model directive provides a convenient way to create two-way data bindings on form input, textarea, and select elements. It essentially combines data binding and event handling into a single directive.
How v-model Works
The v-model directive is syntactic sugar for binding a value to a component and listening for input events to update that value. For a standard input element, the following two examples are equivalent:
<!-- Using v-model -->
<input v-model="userInput">
<!-- Manual implementation -->
<input
:value="userInput"
@input="userInput = $event.target.value">
Using v-model with Custom Components
When using v-model with custom components, Vue expects the component to have a prop named value and emit an event named input. Here's an example of a custom input component that works with v-model:
<div id="app">
<custom-input v-model="counter"></custom-input>
<p>Current value: {{counter}}</p>
</div>
<script>
Vue.component('custom-input', {
template: `
<div>
<input
ref="inputField"
:value="value"
@input="$emit('input', $event.target.value)"
>
</div>
`,
props: ['value']
})
new Vue({
el: '#app',
data: {
counter: 100,
}
})
</script>
Customizing v-model for Special Input Types
For components like checkboxes or radio buttons that use the value attribute differently, you can customize the prop and event names using the model option:
Vue.component('toggle-switch', {
model: {
prop: 'isActive',
event: 'toggle'
},
props: {
isActive: Boolean
},
template: `
<input
type="checkbox"
:checked="isActive"
@change="$emit('toggle', $event.target.checked)"
>
`
})
The .sync Modifier
The .sync modifier provides another way to achieve two-way binding for props. It's particularly useful when you want to update a prop from within a child component. The .sync modifier is syntactic sugar that automatically handles the udpate event.
<!-- Using .sync -->
<child-component :visibility.sync="isVisible"></child-component>
<!-- Equivalent manual implementation -->
<child-component
:visibility="isVisible"
@update:visibility="val => isVisible = val">
</child-component>
To update a prop using .sync, emit an event with the pattern update:propName:
// Inside child component
this.$emit('update:visibility', false);
Practical Example of .sync
Here's a complete example demonstrating the use of .sync with a modal component:
<template>
<div class="container">
<modal-window :display.sync='showModal'
style="padding: 30px 20px 30px 5px; border:1px solid #ddd; margin-bottom: 10px;">
</modal-window>
<button @click="toggleModal">Toggle Modal</button>
</div>
</template>
<script>
import Vue from 'vue'
Vue.component('modal-window', {
template: `<div v-if="display">
<p>Modal is currently {{display ? 'visible' : 'hidden'}}</p>
<button @click.stop="hideModal">Close Modal</button>
</div>`,
props: ['display'],
methods: {
hideModal() {
this.$emit('update:display', false);
}
}
})
export default {
data() {
return {
showModal: true,
}
},
methods: {
toggleModal() {
this.showModal = !this.showModal;
}
}
}
</script>
Key Differences Between v-model and .sync
While both v-model and .sync enable two-way data binding, they have different use cases:
- v-model is designed primarily for form inputs and follows the convention of using
valueprop andinputevent (configurable viamodeloption). - .sync is more flexible and can be used with any prop name, following the
update:propNameevent pattern. - v-model is typically used for a single primary value in a component, while .sync can be used for multiple props simultaneously.
When to Use Each Approach
Use v-model when:
- Creating form-like components (inputs, selects, checkboxes)
- You want the standard input/value pattern
- Working with components that represent a single value
Use .sync when:
- You need two-way binding for props that aren't form inputs
- You want to update multiple props from a child component
- You prefer explicit prop names rather than the generic
value