Vue: comunicación two-way binding entre componentes


  • Share on Pinterest

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:

  1. :value="invoiceDate" aquí se define el valor que viene desde el componente padre por medio de las props.
  2. Al haber un cambio en el campo @input emitirá otro evento por medio de $emit('update:invoiceDate', $event.target.value) (siguiendo el patrón update:propName) para notificar de un cambio en una prop en este caso invoiceDate. Visto de otra manera update:invoiceDate emitirá el valor de @input que su vez actualizará la prop invoiceDate del componente padre.

Información extra y otros ejemplos:

Para Vue 3 usando composition API:

https://dev.to/thomasfindlay/how-to-easily-sync-with-multiple-v-models-in-vue-3-using-composition-api-1pmg