Vue: Support more collection data types in v-for

Created on 28 Feb 2016  ·  39Comments  ·  Source: vuejs/vue

At some situations, plain object isn't the best choise. I tried to render a Map object by v-for, but seems Vue does not support it currently. (Here's a post I created in the Help thread on the forum.)

Hope Vue can provide the for ... of syntax in v-for to iterate over data types like Map and Set.

For example:

const map = new Map();
map.set('key1', 'val1');
map.set('key2', 'val2');

and we can render map in this way:

<ul>
    <li v-for="[key, val] of map">{{key}} - {{val}}</li>
</ul>
feature request

Most helpful comment

It's important to be able to iterate over iterators in loops. That seems plainly obvious. It's a fundamental feature of the language.

The reasons for supporting it are:

1) Iterators, Maps, and Sets are all valid ES6. Refusing to support them means limiting yourself to ES5, which is a decision becoming less and less justified over time.
2) I'm building an application that has internal data stored in Maps and Sets. Instead of making them available to the UI, I now need to keep the data synced between the two manually, or write boilerplate and import it into my templates to do the conversion whenever the data is needed. This is exactly what Vue is intended to avoid.

All 39 comments

A duplicate of https://github.com/vuejs/vue/issues/1319

@wenLiangcan , you could use something like this:

<ul>
    <li v-for="[key, val] of get(map)">{{key}} - {{val}}</li>
</ul>

where get() is your function.

haha! similar issues open all the time and and people insist that they cannot justify implementation well people want to use it that's one justification I can also list the bazillion of issues closed that ask the same thing xD. I also found one that justifies the use-case really well and refers to es6 specification when it comes to map order ->still closed.

People wanting to use a feature is not, only by itself, an argument that can justify the need of having such feature, it's necessary to weigh the cost and benefits (what problem is being solved) of adding such feature

Yep but still the arguments that people use to close the issue are still not valid or at least not valid for all usecases eg the example of the elipen who justified his usecases very well as I mentioned above

If you want to discuss a specific issue, link to it please.

Plus, this Feature issue is open. It does not make sense to have more than one issue open for the same request.

It's important to be able to iterate over iterators in loops. That seems plainly obvious. It's a fundamental feature of the language.

The reasons for supporting it are:

1) Iterators, Maps, and Sets are all valid ES6. Refusing to support them means limiting yourself to ES5, which is a decision becoming less and less justified over time.
2) I'm building an application that has internal data stored in Maps and Sets. Instead of making them available to the UI, I now need to keep the data synced between the two manually, or write boilerplate and import it into my templates to do the conversion whenever the data is needed. This is exactly what Vue is intended to avoid.

Since #1319 is closed, it's worth reiterating on current decision here. In short, the feature is not trivial to implement (on observation mechanism level), so it's not about justifying use cases, it's about the amount of work and trade-offs.

I would really appreciate this feature, too. On the other hand, if observing ES6 data types becomes terribly hacky or, for example, compromise performance or other qualities, then people who do not currently use Maps and Sets with Vue might not appreciate this change.

I suppose using Array.from() inside a computed function will have to be your best friend for now. :disappointed:

Any solution for that?

Small update, this will come if/when Vue decides to drop "legacy" browsers and will move to Evergreen ones with Proxy instead of set/get for reactivity.

@alexsandro-xpt, just use a computed function that returns Array.from(yourDataSet).

@nickmessing I try with Map, doesn't work.
The computed array length value is always 0.

Just Array.from is probably not solution you want because of lack of reactivity (changes to yourDataSet will not get propagated to Vue).

As mentioned earlier, Sets and Maps are not observable by Vue. In order to use those — either in v-for, or in computed properties, methods, watchers, template expressions, etc. — you need to create a serializable replica of this structure and expose it to Vue. Here's a naive example which uses a simple counter for providing Vue with information that Set is updated:

data() {
  mySetChangeTracker: 1,
  mySet: new Set(),
},

computed: {
  mySetAsList() { 
    // By using `mySetChangeTracker` we tell Vue that this property depends on it,
    // so it gets re-evaluated whenever `mySetChangeTracker` changes
    return this.mySetChangeTracker && Array.from(this.mySet);
  },
},

methods: {
  add(item) {
    this.mySet.add(item);
    // Trigger Vue updates
    this.mySetChangeTracker += 1;
  }
}

This illustrates a kinda hacky but 100% working method for making non-observable data reactive. Still, in real world cases I ended up with serialized versions of Sets/Maps (e.g. you'd probably want to store the modified versions of sets/maps in localstorage and thus serialize them anyway), so no artificial counters/hacks were involved.

I personally think this is a fair solution to a problem, but it definitely deserves some official documentation — otherwise it's impossible to justify this as non-hacky way of dealing with Vue internals.

@alexsandro-xpt, sorry, I was wrong, computed will be hacky as @inca said, another hacky solution would be using $forceUpdate with a method, here's an example fiddle

Thank you @nickmessing and @inca, this work fine with my new Map()!!

Right now when you do a "v-for" over a "Map", the v-for acts as if it had received an empty array.

Regardless of the outcome of the extended discussion about whether/how to support Maps and Sets, it would save a lot of people a lot of debugging time if Vue simply warned "Maps and Sets are not yet supported -- see https://github.com/vuejs/vue/issues/2410".

Yup, Google search for this feature brought me to this ticket (after a few annoying mixups with Vue.set)

👍 This should be in the v-for documentation!

Truly, should be in v-for documentation!

/cc @chrisvfritz let's try to add a note about support for these types up in the docs for v-for (both API and the list rendering section) - I'll also take a look at them in 2.5.

@yyx990803 I wonder if a console warning would be better for this, since that would tell people what's wrong immediately, obviating the need to search for the solution.

We're also already very explicit in the docs about which types we _do_ support, Map and Set not being among them. I can definitely see the argument for why one might _hope_ all iterables would work with v-for, but I don't think we currently give readers any reason to expect they would.

I'm not quite seeing the argument against adding support for Set.

Set itself can be cleanly polyfilled, and unless I'm missing something, it seems like Vue's approach for adding reactivity to arrays could very easily be extended to sets. We'd only need to wrap .add(), .clear(), and .delete().

My best guess (please correct/sorry if I'm wrong): the trickiest part is to wrap a Set constructor, which accepts an iterable. I don't see how iterable can be made observable, because in its general form it's just a function (i.e. next) with no referenceable state (think of generator-based iterator as an example).

Why do we need to wrap the constructor? Wouldn't we be passing pre-existing Sets into Vue?

According to the spec, the Set constructor immediately runs through the entire iterator, effectively retaining a shallow copy of the unique items returned by the iterator. Once you've got a Set instance, it shouldn't matter whether it was created from an iterator or not.

In this regard, a Set created from an iterator should be no different from an array created from an iterator (via Array.from()), which Vue already supports.

You can use immutable Maps/sets/ whatever data structures and allow reactivity with them though, but simply because the whole chunk reference changes. You can render them through a render function or computed generated array (the earlier being better in performance as it skips the creation of an array..). But mutable data structures, not so unless you find a way to notify Vue of specific changes manually, which would be simply homebrewing yr own solution.

That's no good. You can't do it

@wenLiangcan

var map = new Map()
  map.set('key1','Test1')
  map.set('key2','Test2')
        <div class="file-size">{{value}}</div>
 </div>

No, it doesn't show up on the page

Keen for support of this : )

Me too

Any future plans to add support? Is there a technical reason Vue could not support Map and Set?

The current problem with the Vue.set method on a plain object is that it triggers way too many subscribers when a property is added to the object. Actually, all the subscribers of all the properties are triggered when only one property is added.

The performance of the view is badly impacted when a map like collection contains hundredth of keys. For example, in my project thousands of subscribers are triggered when one element is added to the map using the Vue.set operation:

Vue.set(state.items, itemId, item); // where items is a plain object.

When I look deeply into the Vue.js code, I can see where the problem comes from. The dependencies that are triggered are those of the object, which means that if the object has one property for each key, then all the dependencies of all the keys are triggered when just adding one key.

So using plain objects to mimic a map does not look as the right solution, and therefore having support for a map in vue is more than welcomed for large collections of items.

Is there any news about future plans and possibly about native Map/Set support?

This article details upcoming support in 2.6 - but there's nothing about that in the official roadmap from what I can see?

https://medium.com/@alberto.park/the-status-of-javascript-libraries-frameworks-2018-beyond-3a5a7cae7513

"The current latest of the core is 2.5.x. Next minor release(v2.6), will support native ESM import, improved async error handling, iterator on ‘v-for’ directive and more."

Not sure where they got that information from?

Found this issue while was debugging the heck Vue's behavior over Set data objects. :thinking:

For people like me that were wondering about the roadmap for this, Evan You says in this video that Map and Set support is "likely" to arrive in 2.6, but that was in May, so that's all I know.

@yyx990803 It's unfortunate that this issue in the tracker is marked as closed, especially if you're considering adding support in the near future. Where can we follow progress on this feature? Is there another issue somewhere?

Just wondering for the sake of argument, and maybe I am doing it wrong, but since you can track array mutation using the Mutation Methods cant you just track an array of an object and be logically complete? you dont get all the same features implemented in Map but the ones you want would be pretty easily addressed especially if you are using something like _ or lodash.

Its sad but until the team adds this we might have to use alternate data structures

Just want to chime in that we were going to use a Map for our data structure, then decided against it because of a lack of first-party support.

is this supported now in Vue3 (that is, reactive Map and Set)?

Was this page helpful?
0 / 5 - 0 ratings