How can I share a method between components in Vue.js?
Javascriptvue.jsJavascript Problem Overview
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!
Javascript Solutions
Solution 1 - Javascript
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
with this.foo = shared.foo.bind(this)
Solution 2 - Javascript
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 - Javascript
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 - Javascript
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);
}
Solution 5 - Javascript
I would just configure a mixin
export const sharedMethods = {
// Generic funcion
methods: {
axiosGet(route){
return axios.get(route)
.then(response => response.data)
.catch((error)=> {console.log(error)})
},
axiosPost(route, postObj, resolveCallback, rejectCallback){
axios.post(route, postObj)
.then(resolveCallback)
.catch(rejectCallback);
},
}
And then use the mixin in the component, calling its functions as you would do with the functions of the component itself, as if the functions were part of the component you are importing the mixin in:
Put it on the component: mixins: [sharedMethods],
Inside the component, I call the mixin function:
this.axiosPost(
'/route',
data,
() => console.log('success'),
() => console.log('fail')
)
It is useful because using an aproach where you just import functions from a js file, the functions in the file being imported will take their own 'this' to use in the functions which might cause some confusion, whereas mixin will use the 'this' of the component it is being imported in, as if its functions were part of the component, as I said.