Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share "computed" methods across a Vue.js application

I have a Vue.js application which loads a list of items, and each item is passed as a prop to a Vue component.

I figured out that by using mixins I can share common component properties, like computed,created, etc.

Now, I'm trying to sort the list of items and can't figure out how I would access each component's computed properties to apply sorting/filtering. How can I accomplish this?

Items

[{
  price: 10,
  qty: 2
}, {
  price: 8,
  qty: 3
}]

Mixin - ./Cost.js

export default {
  computed: {
     cost () {
        return this.price * this.qty;
     }
  }
}

Component (which works as expected) - ./Product.vue

import Cost from './Cost.js'
export default {
   name: 'product-item',
   props: ['product'],
   mixins: [Cost]
}

How would you access the computed properties, or restructure this setup?

List component

<template>
  <div id="list">
     <div v-for="product in sorted" :product="product">Cost: {{ cost }} </div>
  </div>
</template>

<script>
import ProductItem from './Product.vue'
export default {
   components: { ProductItem },
   created: () {
      this.items = [...] // as noted above
   },
   computed: {
       sorted () {
           return this.items.sort( (a,b) => b.cost - a.cost); // cost is not accessible!
       }
   }
}
</script>
like image 393
d-_-b Avatar asked Sep 18 '18 22:09

d-_-b


2 Answers

Use vuex. Your vuex store will provide a getters object that can be wrapped into multiple components’ native computed objects, or accessed directly. Your code will be DRY, reactive, cached, and maintainable.

From my experience, once you need to go beyond child-parent data relationships, vuex, store, and shared state are the way to go. Once you get the hang of it, it is downright magical how your app evolves.

It is beyond scope of the question to show how to install vuex. Visit https://vuex.vuejs.org/guide/getters.html to see how getters are similar to computed properties, with the value of being shared between components. The official Vuex guide will also demonstrate how to initialize your Vue instance with the store.

Here are some snippets to show you the actors in the vuex system.

Store and State

// state definition (basically a shared reactive 'data' object that lives outside components)
state:{
    message:'Hello'
}

// the store getters are declared as methods and accessed as properties (just like component/computed)
getters:{
    message: state => return state.message
}

Accessing From Components

// component 1 wraps getter
computed:{
    message(){
      return this.$store.getters.message
    }
}

// component 2 also wraps getter
computed:{
    message(){
      return this.$store.getters.message
    }
}
// templates can also use getters directly
<div>{{$store.getters.message}}</div>

// If message was wrapped, you can simply use the computed property
<div>{{message}}</div>

Once you start using vuex, all sorts of other treasures start to emerge, such as the developer tools in Chrome, undo/redo support, simple refactoring of state, time-travel debugging, app persistence, etc. There are also shortcuts for adding multiple store getters into your computed properties.

like image 124
Steven Spungin Avatar answered Sep 30 '22 06:09

Steven Spungin


As suggested by @Sphinx, you could use a ref to access the child component.

For example:

<template>
  <div id="list">
     <product-item v-for="product in sorted" :product="product" :ref="product"></product-item>
  </div>
</template>
<script>
import ProductItem from './Product.vue'
export default {
   components: { ProductItem }, 
   data: () => ({
       hidrated: false,
       items: []
   })
   created() {
       this.items = [...] // as noted above
   },
   mounted() {
       this.hidrated = true
   },
   computed: {
       sorted () {
           if (!this.hidrated && !Object.keys(this.$refs).length) {
               // handle initial state, before rendered
               return this.items
           }
           return Object.values(this.$refs)[0]
               .sort((a,b) => b.cost - a.cost)
               .map(c => c.product)
       }
   }
}
</script>

This is assuming you have no other ref in your List Component.

You also have to check if the component is rendered first, here I use hidrated to flag when the component is mounted.

like image 35
Wildan Maulana Syahidillah Avatar answered Sep 30 '22 06:09

Wildan Maulana Syahidillah