Comunicar componentes (two-way data binding) padre e hijo por medio de sus props sin el problema de sincronización, es decir, que el padre o el hijo no estén al tanto cuando exista un cambio.
Caso de estudio:
Estamos editando un formulario, suponiendo que el componente padre hace una consulta a la api para traer los datos del registro, posteriormente se mapea el objeto llamado “invoiceData” y queremos que este se mantenga actualizado con los cambios en el componente hijo:
Componente Padre:
<template>
<div class="container">
<form @submit.prevent="storeInvoice()">
<div class="row">
<div class="col-md-12">
<header-fields-component
...
:invoice-date.sync="invoiceData.invoice_date"
:due-date.sync="invoiceData.due_date"
/>
</div>
...
</template>
<script>
...
import HeaderFieldsComponent from "./HeaderFieldsComponent";
...
export default {
components: {
HeaderFieldsComponent,
...
},
...
data() {
return {
...
invoiceData: {
invoice_number: "0000000",
client_id: null,
invoice_date: null,
due_date: null,
data: {
items: []
},
total: 0.0
}
};
},
...
Luego, definimos las props del componente hijo:
(Seguimos dentro de componente padre)
...
<header-fields-component
:invoice-date.sync="invoiceData.invoice_date"
:due-date.sync="invoiceData.due_date"
/>
...
Nota: Es muy importante observar que después de definir una prop en el componente le sigue el modificador “.sync” esto le dice a Vue que esté atento a los cambios en esa prop en especifico.
Ahora, dentro del componente hijo nos interesa que los campos de tipo “date” reciban el valor del padre, pero que cuando estos tengan un cambio de igual manera lo comuniquen al mismo:
HeaderFieldsComponent.vue (Componente hijo)
<template>
...
<div class="form-group mr-3">
<label class="mr-3" for="invoice_date_input">Invoice date</label>
<input
type="date"
class="form-control mb-2 mr-sm-2"
id="invoice_date_input"
required
:value="invoiceDate"
@input="$emit('update:invoiceDate', $event.target.value)"
/>
</div>
<div class="form-group mr-3">
<label class="mr-3" for="due_date_input">Due date</label>
<input
type="date"
class="form-control mb-2 mr-sm-2"
id="due_date_input"
required
:value="dueDate"
@input="$emit('update:dueDate', $event.target.value)"
/>
</div>
</div>
</template>
<script>
...
export default {
props: {
...
invoiceDate: {
type: String
},
dueDate: {
type: String
},
},
...
Como se ve, en el componente hijo están definidas sus props y los campos que nos interesa comunicar con el padre. Ahora veamos como están definidos el atributo value de los campos y su valor, también como se emite un evento desde el listener “@input” para notificar de los cambios al padre:
<input
type="date"
...
:value="invoiceDate"
@input="$emit('update:invoiceDate', $event.target.value)"
/>
<input
type="date"
...
:value="dueDate"
@input="$emit('update:dueDate', $event.target.value)" />
Es importante notar los siguiente:
:value="invoiceDate"
aquí se define el valor que viene desde el componente padre por medio de las props.- Al haber un cambio en el campo
@input
emitirá otro evento por medio de$emit('update:invoiceDate', $event.target.value)
(siguiendo el patrónupdate:propName
) para notificar de un cambio en una prop en este casoinvoiceDate
. Visto de otra maneraupdate:invoiceDate
emitirá el valor de@input
que su vez actualizará la propinvoiceDate
del componente padre.
Información extra y otros ejemplos:
- https://medium.com/front-end-weekly/vues-v-model-directive-vs-sync-modifier-d1f83957c57c
- https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
- https://codesandbox.io/s/vue-template-forked-z6k9c?file=/src/components/SampleComponent.vue
Para Vue 3 usando composition API: