Enviar datos entre componentes hermanos en Vue.js
En Vue.js se envían datos desde un componente padre hacia el hijo mediante sus propiedades ( props
) y desde el componente hijo hacia el padre mediante eventos ( $emit
). Con estas dos formas de enviar datos que hemos visto en artículos anteriores tendríamos lo necesario para pasar un dato desde un componente a otro que esté a su mismo nivel. Sin embargo existe una forma mucho más eficiente para enviar información entre componentes hermanos y en general entre componentes sin importar su grado de anidación. El día de hoy veremos el bus de datos.
Pasando datos sin el bus de eventos mediante métodos
Esta forma de pasar datos entre dos componentes hermanos requiere que uno de los componentes envíe un evento que sea utilizado por el padre para recibir los datos en un método y enviarlos mediante las propiedades al otro componente. Veamos esto paso a paso.
Primero, supongamos que tenemos un componente p-item
que capturará el valor de un artículo y otro componente invoice-summary
que mostrará el valor del artículo y los impuestos o taxes. El segundo componente solo recibirá la información tal y como lo puedes ver a continuación.
Vue.component("invoice-summary", {
props: ['amount', 'taxes'],
template: `
<p>
Amount: {{ '\{\{ amount \}\}' }} <br />
Taxes: {{ '\{\{ taxes \}\}' }}
</p>
`
});
Ahora bien, este componente será hermano de otro componente que capturará la información. Entonces nuestro HTML en donde estarán estos componentes será el siguiente:
<div id="app">
<p-item
name="iMac Pro"
v-on:value-changed="updateTotals"></p-item>
<invoice-summary
:amount="totalAmount"
:taxes="totalTaxes"></invoce-summary>
</div>
La clave aquí es ver que invoce-summary
solo recibe parámetros. Por otro lado, p-item
estará escuchando el evento value-changed y en ese caso ejecutará la función updateTotals
que actualizará los valores de totalAmount
y totalTaxes
que finalmente serán pasados al componente.
var vue = new Vue({
el: "#app",
data: {
totalAmount: 0,
totalTaxes: 0
},
methods: {
updateTotals: function(val, tax) {
this.totalAmount = val;
this.totalTaxes = tax;
}
}
});
Por su parte, el componente p-item
deberá emitir el evento respectivo con los valores necesarios para la función updateTotals
.
Vue.component("p-item", {
props: ['name'],
data: function () {
return {
value: 1500
}
},
template: `
<div>
{{ '\{\{ name \}\}' }}
<input type="number"
v-model.number="value" step="100" />
</div>
`,
computed: {
tax: function() {
return this.value * 0.2;
}
},
watch: {
value: function() {
this.$emit('value-changed', this.value, this.tax);
}
},
created: function() {
this.$emit('value-changed', this.value, this.tax);
}
});
A continuación puedes ver este ejemplo corriendo en codepen.
Pasando datos con el bus de eventos
Un bus de eventos no es más que otra instancia de Vue. Una instancia que puede tener las mismas propiedades que cualquier otra instancia. Vamos a hacer un cambio simple en el anterior ejemplo y vamos a crear una instancia para generar en ella el evento value-changed. Para esto debemos agregar esta línea en nuestro script.
var bus = new Vue({});
Por otro lado el componente p-item
deberá emitir eventos sobre esta instancia y no sobre su misma instancia.
watch: {
value: function() {
bus.$emit('value-changed', this.value, this.tax);
}
},
created: function() {
bus.$emit('value-changed', this.value, this.tax);
}
Como el bus es una instancia diferente ya no tiene mucho sentido conservar el evento en nuestro HTML, por lo cual se simplificaría de la siguiente manera:
<div id="app">
<p-item name="iMac Pro"></p-item>
<invoice-summary
:amount="totalAmount"
:taxes="totalTaxes"></invoce-summary>
</div>
Finalmente debe agregarse un hook a nuestro componente raíz para escuchar el evento en cuestión y hacer lo que antes hacía el evento que quitamos.
created: function() {
bus.$on('value-changed', this.updateTotals);
}
Puedes ver este ejemplo corriendo en codepen a continuación con el mismo resultado que el anterior.
Sin embargo, si te das cuenta aún tenemos un método llamado updateTotals
en nuestro componente raíz. Lo cual indica que deberíamos tener este método en cualquier componente en donde querramos usar p-input
y es precisamente aquí en donde toma importancia el bus. Recuerdas que el bus de eventos es como cualquier instancia de vue ?, esto significa que podemos pasar este método allí junto con las propiedaes.
var bus = new Vue({
data: function() {
return {
totalAmount: 0,
totalTaxes: 0
}
},
methods: {
updateTotals: function(val, tax) {
this.totalAmount = val;
this.totalTaxes = tax;
}
}
});
Hecho esto nuestro componente raíz quedaría así:
var vue = new Vue({
el: "#app",
created: function() {
this.bus = bus;
bus.$on('value-changed', bus.updateTotals);
}
});
Nota que hemos asignado el bus a una propiedad de la instancia para poder en el HTML hacer un bind de los atributos.
<div id="app">
<p-item name="iMac Pro"></p-item>
<invoice-summary
:amount="bus.totalAmount"
:taxes="bus.totalTaxes"></invoce-summary>
</div>
A continuación el ejemplo en codepen con los mismos resultados.