How can I share a method between components in Vue.js?
Check out this simple shopping cart demo:
http://plnkr.co/edit/CHt2iNSRJAJ6OWs7xmiP?p=preview
A user can pick a veggie and a fruit, and it will be added into the cart array. The function that adds a fruit/veggie is very similar, and I want to combine it into a function that can be shared across both components.
selectFruit: function(product){
var cart = this.cart
for(p in cart){
if (cart[p]["type"] == "fruit"){
console.log("We already got a fruit!, Let's remove " + cart[p]["name"] + " and add in " + product["name"]);
this.cart.$remove(cart[p])
}
}
console.log("Adding " + product.name + " to cart.");
var productName = product.name
var cartFruit = {name: product.name, type: 'fruit'}
this.cart.push(cartFruit)
}
selectVeggie: function(product){
var cart = this.cart
for(p in cart){
if (cart[p]["type"] == "veggie"){
console.log("We already got a veggie!, Let's remove " + cart[p]["name"] + " and add in " + product["name"]);
this.cart.$remove(cart[p])
}
}
console.log("Adding " + product.name + " to cart.");
var productName = product.name
var cartVeggie = {name: product.name, type: 'veggie'}
this.cart.push(cartVeggie)
}
How can I make it so I can alter this method and have it used globally? I'm using the Vue Router with this project btw, thanks for any help!
Solution 1:
I found this technique to be more simple/satisfactory, as I prefer composition over inheritance:
src/shared.js
export default {
foo: function() { alert("foo!") }
}
src/yourcomponent.vue
<template>...</template>
<script>
import shared from './shared'
export default {
created() {
this.foo = shared.foo // now you can call this.foo() (in your functions/template)
}
}
</script>
This will also allow you to write Vue-agnostic tests.
NOTE: if you need foo to run in Vue-scope replace
this.foo = shared.foo
withthis.foo = shared.foo.bind(this)
Solution 2:
Option 1
One approach for sharing your method across components is to use a mixin. Here's a cartMixin
that contains a selectProduct
method:
var cartMixin = {
methods: {
selectProduct: function (product) {
var cart = this.cart
for(p in cart){
if (cart[p]["type"] == product.type){
console.log("We already got a "+ product.type +"!, Let's remove " + cart[p]["name"] + " and add in " + product["name"]);
this.cart.$remove(cart[p])
}
}
console.log("Adding " + product.name + " to cart.");
var productName = product.name
var cartProduct = {name: product.name, type: product.type}
this.cart.push(cartProduct)
}
}
};
You can reference this in each component like this:
var Vegetable = Vue.extend({
template: '#vegetable',
mixins: [cartMixin],
data: function(){
return sourceOfTruth
}
})
... and then use it in your templates like this:
<li v-for="product in food | showOnly 'fruit'" @click="selectProduct(product)">
{{product.name}}
</li>
Here's a fork of your Plunker.
Option 2
After thinking about this some more, another option you might consider is to create a base Product
component and extend that to create your Fruit
and Vegetable
components. You would then put your common functionality in the base component.
var Product = Vue.extend({
data: function(){
return sourceOfTruth
},
methods: {
selectProduct: function (product) {
var cart = this.cart
for(p in cart){
if (cart[p]["type"] == product.type){
console.log("We already got a "+ product.type +"!, Let's remove " + cart[p]["name"] + " and add in " + product["name"]);
this.cart.$remove(cart[p])
}
}
console.log("Adding " + product.name + " to cart.");
var productName = product.name
var cartProduct = {name: product.name, type: product.type}
this.cart.push(cartProduct)
}
}
})
var Vegetable = Product.extend({
template: '#vegetable',
});
var Fruit = Product.extend({
template: '#fruit',
});
Here's a Plunker with this approach.
Given that your Fruit and Vegetable templates are so similar, you might be able to take this idea even further and use a common template from the base component.
Solution 3:
If you are trying to share the same component logic between or along multiple vue template and layout, you can simply try this approach below:
before:
a.vue
<template>
<div>
<h1>{{title}}</h1>
<small>{{datetime}}</small>
</div>
</template>
<script>
export default {
props: {
title: String
},
data() {
return {
datetime: new Date()
}
}
}
</script>
b.vue
<template>
<div>
<h3>{{title}}</h3>
<h6>{{datetime}}</h6>
</div>
</template>
<script>
export default {
props: {
title: String
},
data() {
return {
datetime: new Date()
}
}
}
</script>
after:
a.vue
<template>
<div>
<h1>{{title}}</h1>
<small>{{datetime}}</small>
</div>
</template>
<script>
import shared from "./shared.js";
export default Object.assign({}, shared);
</script>
b.vue
<template>
<div>
<h3>{{title}}</h3>
<h6>{{datetime}}</h6>
</div>
</template>
<script>
import shared from "./shared.js";
export default Object.assign({}, shared);
</script>
shared.js
export default {
props: {
title: String
},
data() {
return {
datetime: new Date()
}
}
}
Solution 4:
You can put the method in your root Vue instance and then dispatch an event from the child instance when a veggie is selected, or when a fruit is selected. Events look for a handler on their parent component, and if they don't find an event handler they keep going up the chain until they do. So on your root instance:
events: {
'choose-fruit':function(fruit){
//handle the choosing of fruit
}
}
Then on the child instance:
selectFruit: function(product){
this.$dispatch('choose-fruit', product);
}