Vue: Allow computed properties of subobjects.

Created on 4 Dec 2015  Â·  9Comments  Â·  Source: vuejs/vue

I want something like this:

{
  data() {
    return {
      song: {
        tracks: [...]
      }
    }
  },
  computed: {
    'song.duration'() {
      return max(this.song.tracks.map(track => track.duration))
    }
  }
}

in such a way that duration is an actual (computed) property on the song object.

I have a feeling this may be quite easy to add. With some pointers where to start, I'd be happy to try to add this myself.

feature request

Most helpful comment

  1. I don't think components should add random getters the raw data objects they observe. If I pass an object to a component, I expect the component to just observe it but not add stuff to it.
  2. Note you can return an object from the computed property too, e.g. songWithExtraStuff (feel free to pick a better name) and pass it around:
computed: {
  songWithExtraStuff () {
    const song = this.song
    return {
      ...song,
      duration: max(song.tracks.map(track => track.duration))
    }
  }
}

All 9 comments

This has been suggested several times before, but my opinion is that nested computed properties can be confusing because:

  1. Does it actually get defined on the data object? Polluting raw data sounds bad to me. If not, then how does this.song.duration work?
  2. this inside nested computed properties can be confusing - should it be the vm or the song object?

IMO nested computed properties do not provide any substantial benefits over flat ones with proper naming, e.g. songDuration.

  1. Yes. It doesn't feel like pollution to me.
  2. With the above syntax I don't see this confusion. It should be the vm
    like other computed properties.

The advantage I see is that I can pass along the song object to another
component. This is cumbersome with a songDuration property.
On Fri, 4 Dec 2015 at 17:19, Evan You [email protected] wrote:

This has been suggested several times before, but my opinion is that
nested computed properties can be confusing because:

  1. Does it actually get defined on the data object? Polluting raw data
    sounds bad to me. If not, then how does this.song.duration work?
  2. this inside nested computed properties can be confusing - should it
    be the vm or the song object?

IMO nested computed properties do not provide any substantial benefits
over flat ones with proper naming, e.g. songDuration.

—
Reply to this email directly or view it on GitHub
https://github.com/vuejs/vue/issues/1964#issuecomment-162009306.

  1. I don't think components should add random getters the raw data objects they observe. If I pass an object to a component, I expect the component to just observe it but not add stuff to it.
  2. Note you can return an object from the computed property too, e.g. songWithExtraStuff (feel free to pick a better name) and pass it around:
computed: {
  songWithExtraStuff () {
    const song = this.song
    return {
      ...song,
      duration: max(song.tracks.map(track => track.duration))
    }
  }
}

Sorry to be a little late to the party here... but isn't this terribly expensive? It looks like any change to any part of song will cause songWithExtraStuff() to re-run and update the duration property of _every song_! And then maybe update all of those duration properties (or maybe all song properties?!?) even if they haven't changed, maybe? I am far to new to vue to know if that is how it works or if it would even have noticeable performance impact.

In any event I can now have calculated properties on my nested objects that update appropriately. Thank you very much for the example.

As mentioned by @acklenx just mixing original properties into computed property is not a good solution as it will lead to unnecessary computations when any of original's property has changed.

Showcase – in this fiddle withStuff.d property depends only on original.a and original.b properties however is computed even if another property, such as original.c was changed.

Simple use-case of when extending computed property looks totally fine and organic is getting fullName for some user from the component's data: user.fullName = user.firstName + user.lastName. Such things used to be accomplished with filters a while ago, but since they are not really preferred way of doing this (as well as regular methods), extending user would be the good spot to do this. Given large components that represent pages with multiple users, storing fullNames in the flat structure is cumbersome.

Another real-life use-case that actually brought me here is own namespace for properties passed from the outside modules (Vuex state for example). As stated by @baberuth22 in this issue, nested properties with methods would provide more flexibility to distinguish common outside properties from the direct component ones.

I know that I'm way late, but I just had the same issue and after an hour of pulling my hear out making Babel to understand spread operator and thinking how I don't want to have a duplicate object - I have arrived to the following conclusion:

computed: {
    'song.duration'() {
      return max(this.song.tracks.map(track => track.duration))
    }
}

will become

computed: {
    trigger() {
      this.song.duration = max(this.song.tracks.map(track => track.duration))
    }
}

Also put {{trigger}} or :="trigger" somewhere in html.

"this way you just pollute your scope with one variable which will never be set" I thought, but if you check your $data - song_duration will not be there. Which makes my answer better than @yyx990803
Are you proud of me guys? :P

P.S. If you try to add a "trigger" prop to data - Vue will still flip on you! Moreover, it looks like a loophole, but it works O_o

Work for me:

data() {
  return {
    form: {
      budget: 'unlimited'
    },
    budgetCost: 999.99,
    budgetType: 'unlimited'
  }
}

computed: {
  budget() {
    return this.budgetType === 'unlimited' 
      ? 'unlimited'
      : this.budgetCost
  }
}

watch: {
  budget(val) {
    this.formData.budget = val
  }
}

@AlexGrump the watch can be dropped entirely if you break your computed property into a get() and set()

data() {
    return {
        form: {
            budget: 'unlimited'
        },
        budgetCost: 999.99,
        budgetType: 'unlimited'
    }
},

computed: {
    budget: {
        get() {
            return this.budgetType === 'unlimited'
                ? 'unlimited'
                : this.budgetCost
        },
        set(val) {
            this.formData.budget = val
        }
    }
}

Work for me:

class Songs {
    constructor (tracks) {
        this.tracks = tracks
    }
    get duration () {
        return Math.max(...this.tracks.map(track => track.duration))
    }
}

export default {
  data () {
    return {
      song: new Songs([...])
    }
  }
}
Was this page helpful?
0 / 5 - 0 ratings