Best way to make vue computed properties available globally?

I have a to-do list with items.
Each item has one Vue-component with computed properties:
eg.

computed: {
  isDue () {
    return (daysAgo(this.item.dueDate) > 0) 
  }
}

However, each Vue-component exists out of say 50 sub-components + I need to used this isDue property in a lot of these components & places.

How do I make these computed properties easily available everywhere?

Stats: on average about 1,000 to-do items per user with each about 50 computed properties.

Option 1:

create a new Vue() for each single to-do item and save it to the window eg.

function itemComputedprops (id) {
  if (!window.cachedVueInstances[id]) {
    return new Vue({id, computedProps})
  }
  return window.cachedVueInstances[id]
}

Option 2:

save the vue component reference to the window?
eg.

mounted () {
  window.itemComponents[this.id] = this.$ref
}

I think this option is less heavy on the Vue debugger (I’m not sure)
But I’m not sure how to best do this method when the app’s to-do items can be filtered and hidden etc. (potentially destroying the vue-component), but I need to make sure these computed props are always available.

Other options opposed to window

Both these two options above - a new vue instance OR a vue component - I save them to the window.

What is another good location to save these if I’d also need to access them in regular JS files?

Not an option:

Use Vuex

Note that getters accessed via methods will run each time you call them, and the result is not cached.

Hi,

If Vuex is not an option because some getters will not be cached you may need to reconsider.

For a start your use case seems to be exactly what Vuex does.

Then if I were you I wouldn’t even worry one second about low-level performance like “what is going to be recalculated” as both browsers engines and Vue are vastly optimized by talented people and can handle a lot, like A LOT of operations.

Writing an app like you describe I guarantee the bottleneck won’t be the stack but some under optimized code you will write down the road. Take a read at this post Vuex best practices for complex objects that will give a sane structure to your project.

1 Like

you state Vuex is not an option, yet the reason you cited, does not even apply to your example of the computed property. Vuex is what you need and it seems you are overthinking this.

2 Likes

Dear @bolerodan and @mikepatton75

First of all let me say how excited I am to receive some input from you guys! I’ve been having this discussion with so many friends and I never was able to solve it.

Let me explain why I cannot use vuex as solution. I read that post on “vuex best practices” long ago and also am not using nested structures. I still am not able to use vuex getters for what I need and I’ll show you why:

Premise

Say I have like about 1000 to-do items. When I open the app the to-do items get retrieved from the server and saved inside my vuex module inside an object where the prop-name is the ID of the item.

const itemsModule = {
  state: {
    data: { // each item gets added here
      '001': {id: '001', text: 'do this', plannedTime: 1000, usedTime: 200},
      '002': {id: '002', text: 'do that', plannedTime: 9000, usedTime: 500},
    }
  }
}

This way I can easily display any item, get it’s data, show it in vue components, and do much more.

However, as you know my goal is to have computed properties per item which I can access in any Vue component.

Method 1: A getter function

const itemsModule = {
  // state
  getters: {
    timeLeft: (state, getters) => (id) => {
      const item = state.data[id]
      return item.plannedTime - item.usedTime
    }
  }
}

The problem with Method 1

The timeLeft property is a function so the results do not get cached. If I use this prop all over my components, and there are updates to the data, then each place I used this getter will need to be re-run individually.

From the vuex documentation:

Note that getters accessed via methods will run each time you call them, and the result is not cached.

(PS: I’ve even tested this with adding a console.log, and the results are really not cached)

Method 2: A getter for all items with computed props

const itemsModule = {
  // state
  getters: {
    itemsWithComputedProps: (state, getters) => {
      return Object.values(state.data).map(item => {
        item.timeLeft = item.plannedTime - item.usedTime
        return item
      })
      // then reduce into an object again with id as prop key
    }
  }
}

The problem with Method 2

When the underlying data on just one item is changed (eg. the usedTime of one single item is changed), the getter will trigger to re-calculate itself and thus calculates the timeLeft on every single item again.

So these both methods are fine in a small app. But imagine having tens of thousands of data rows that all need some form of computed properties calculated client side.

(PS: I’ve even tested this with adding a console.log, and the data for each item are really re-calculated)

To conclude

And finally to come back to my problem:
You see why vuex is not an option. That’s why I thought using a vue-component, 1 per item, without html template, then find a way to expose the computed props globally.

Do you still recommend Vuex knowing this?

Is there something I am missing? Is there some way vuex can have id-based getters and still cache the results?

Hi,

Is there something I am missing?

https://jsfiddle.net/5v1aL7yu/2/

Yes. Component caching is a powerful thing :slight_smile:

Also if you are going to display 10 thousands items on screen don’t forget to use something like https://github.com/tangbc/vue-virtual-scroll-list if you want to keep proper performance.

Have fun using Vuex !

1 Like

So exactly as in your fiddle, the getter is not cached and thus my main problem is still not solved.

To demonstrate for you, I have added the getter a couple of times in the template, and as you can see in the console.log it gets run & calculated every time. (this is exactly the problem with Method 1 above)

https://jsfiddle.net/yez81co0/

So because getters that return functions in vuex are not cached, if I want to use this same getter in a lot of places I would need to use the computed property instead. (which does get cached)

And thus my original question remains: How do I make computed properties easily available everywhere? in any component or even other javascript files.

I’m currently still using my “Option 1” from the original post, because I can think of no better way…

So exactly as in your fiddle, the getter is not cached and thus my main problem is still not solved.

Yes it is, the getter is random yet it’s four times the same value. Only the matching computed is called, what could be less ?

To demonstrate for you

The only thing you’ve demonstrated is that you misunderstand how component architecture works. If you do things wrong yes your application will be bloated. But we are here to help.

if I want to use this same getter in a lot of places

Precise your use case. Unless you plan to fill the screen with more information than it can possibly contain and update all of it several hundreds times per second it should fit.

And thus my original question remains: How do I make computed properties easily available everywhere?

The only important question is How do I structure my complex application so that it’s not bloated ?

I think you’re chasing a unicorn here. You have a forum leader saying you it fits, a concrete proof of it and thousands of major complex projects happily running it. Can’t do more for you friend, maybe you’ll find something faster here https://stefankrause.net/js-frameworks-benchmark8/table.html or develop it yourself after all.

Perhaps a solution of keeping these computed properties, component bound instead of vuex bound is the best way for you. And if you dont want to duplicate computed propery code, create a Mixin that your components inherit in. Do the time diff directly in the computed property, not as a function ID lookup method in vuex getters.

1 Like

I would be curious about a use case that would justify this for performance reason.

Hey Luca,

My guess from conversations before (and seeing some of your code) is that you’re probably over-saturating your app with computed properties in general hence this requirement for performant properties as everything is running slowly anyway - am I close to the mark?

If I’m right (and I may not be!) I would probably be tempted to take a step back and say - do you really need them?

Let’s say an item is due. Does that mean this second, this minute, this hour? What is the resolution of urgency?

Could you pre-calculate this per page load, rather than per update?

Could you manually recalculate this when a user updates the time?

Could you refresh the page every minute?

My guess is that isDue does not need to be reactive at all, so just render it and don’t worry about frequent updates.

Also, FWIW, comparing timestamps is more performant:

const now = Date.now()
data() {
  return {
    isDue: this.item.timestamp < now 
  }
}
1 Like

No. It’s not cached. A method style getter cannot be cached because the getter returns a function, not a value. Your example doesn’t show any form of caching. E.g. if you add another row, each item’s computed is recalculated. If it were cached, each item would reuse its cached value. https://jsfiddle.net/jamesbrndwgn/5v1aL7yu/13/

2 Likes

An “easy” solution would be to just put the computed property inside your App.vue and every time the property changes you set a store object to this value.

For example I mostly just use a simple custom store. Inside your computed property function you update the value inside the store.