New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iterate maps with ng-for #2246

Closed
rolandjitsu opened this Issue May 31, 2015 · 46 comments

Comments

Projects
None yet
@rolandjitsu
Copy link

rolandjitsu commented May 31, 2015

I am trying to iterate over Map values with the built in ng-for, but I can see that for some reason it is not working.

In the specs, you can iterate over a Map iterable with:

let map = new Map();
let values = map.values();
for (let item of values) {
    console.log(item);
}

And none of the ES5 standard for (...)/for (... in ...) will work with that.

Does the built in ng-for actually support iterating over maps?

@mhevery

This comment has been minimized.

Copy link
Member

mhevery commented May 31, 2015

Maps have no orders in keys and hence they iteration is unpredictable. This was supported in ng1, but we think it was a mistake and will not be supported in NG2

The plan is to have a mapToIterable pipe

<div *ng-for"var item of map | mapToIterable">

@mhevery mhevery closed this May 31, 2015

@rolandjitsu

This comment has been minimized.

Copy link

rolandjitsu commented May 31, 2015

@mhevery sounds fair enough. Can I already make use of a pipe inside ng-for that would transform that map into an array for instance? Or that has not been implemented yet?

@gdi2290

This comment has been minimized.

Copy link
Member

gdi2290 commented May 31, 2015

@rolandjitsu this is kinda supported if you write it like this example

  <ul>
    <li *ng-for="var item of myMap.values(); var i = index">
    {{ item | json }} {{ i | json }}
    </il>
  </ul>
  <ul>
    <li *ng-for="var item of myMap.entries(); var i = index">
    {{ item | json }} {{ i | json }}
    <br>
    {{ item[0] | json }} {{ item[1] | json }}
    </il>
  </ul>
@Component({ selector: 'app' })
@View({ directives: [NgFor], template: template })
export class App {
  myMap: any;
  constructor( ) {
    this.myMap = new Map();
    this.myMap.set('Some Key', 'Some Value');
  }
}

@mhevery inside of *ng-for this doesn't work var i = index; var mykey=item[0]; var value=item[1]

<li *ng-for="var item of myMap.entries(); var i = index; var mykey=item[0]; var myvalue=item[1]">
  {{ mykey | json }}: {{ myvalue | json }}
</li>

compiler blows up when it sees [. This example doesn't work with JitChangeDetection with changeDetection: ON_PUSH but the default one is fine.

Oddly enough, the original example works in changeDetection: ON_PUSH and JitChangeDetection until ChangeDetection does another tick then it removes them

<template type="text/ng-template">
  <li *ng-for="var item of values">
</template>

<script type="text/ng-typescript">
@Component()
@View()
class App {
  constructor() {
    let map = new Map();
    map.set('Some Key', 'Some Value');
    this.values = map.values();
  }
}
</script>

Tipe CMS

@rolandjitsu

This comment has been minimized.

Copy link

rolandjitsu commented Jun 1, 2015

@gdi2290 I experience the same thing, it renders them but then they're gone.

P.S.:I do not fully understand the internals of the change detection (like when the next tick occurs).

@gdi2290

This comment has been minimized.

Copy link
Member

gdi2290 commented Nov 23, 2015

Whenever you mutate the component, change the value of one of it's properties, then angular's change detection will be able to detect the changes in the component tree. If you want to learn more about ChangeDetection that's a post by Victor Savkin. I'm not sure why the side-effect above happens with JIT

Tipe CMS

@e-oz

This comment has been minimized.

Copy link

e-oz commented Nov 25, 2015

Not supporting this is a mistake in ng2. It's a very often case when you don't care about order, you just need indexes of object for direct access (to display value in "View" mode, such as {{someList[someModel.id]}}), and to show options for select, <option *ng-for="#item of someList" [value]="item.id">{{item.title}}</option>.

@obscur666

This comment has been minimized.

Copy link

obscur666 commented Dec 3, 2015

try to use this pipe

@Pipe({ name: 'values',  pure: false })
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key]);
  }
}

<div *ng-for="#value of object | values"> </div>
@e-oz

This comment has been minimized.

Copy link

e-oz commented Dec 3, 2015

@obscur666 thank you. I did it already, but it means we multiply *2 size of list in memory and had to use more lines of code.

@marcj

This comment has been minimized.

Copy link

marcj commented Dec 21, 2015

Not supporting this is a mistake in ng2

Jupp, especially when you take into consideration that all current modern browser sustain the key order of objects (except if you use numbers as key, but that is known). Libraries and developer are used to work with objects that way (especially for configurations). Angular's decision is based on theory, but the practice tells us something different. They should rather adjust the Javascript specification so Browsers officially aren't allowed to change the order theoretically anymore. ;)

@mhevery

This comment has been minimized.

Copy link
Member

mhevery commented Dec 26, 2015

this can be better supported by a pipe which converts an object into array
and sorts the keys.

On Sun, Dec 20, 2015 at 6:06 PM Marc J. Schmidt notifications@github.com
wrote:

Not supporting this is a mistake in ng2

Jupp, especially when you take into consideration that all current modern
browser sustain the key order of objects (except if you use numbers as key,
but that is known). Libraries and developer are used to work with objects
that way (especially for configurations). Angular's decision is based on
theory, but the practice tells us something different. They should rather
adjust the Javascript specification so Browsers officially aren't allowed
to change the order theoretically anymore. ;)


Reply to this email directly or view it on GitHub
#2246 (comment).

@jhiemer

This comment has been minimized.

Copy link

jhiemer commented Jan 19, 2016

@obscur666 when using your Pipe I ran into:

Expression 'entity.props | values in Component@140:26' has changed after it was checked.

This whole tweaking really feels a bit buggy in Angular 2. Are there more solid approaches?

@mhevery

This comment has been minimized.

Copy link
Member

mhevery commented Jan 19, 2016

@jhiemer if it is a bug, then it should be filed, and fixed, rather than worked around it.

@jhiemer

This comment has been minimized.

Copy link

jhiemer commented Jan 19, 2016

@mhevery the thing is, reading through other tickets this does not seem to be a bug. When I get an object and transform it into a array I need to return a new object, which results into the error.

http://stackoverflow.com/questions/34313743/expressionchangedafterithasbeencheckedexception-since-angular-2-beta

This behaviour is described in different tickets/stackoverflow entries.

@mhevery

This comment has been minimized.

Copy link
Member

mhevery commented Jan 19, 2016

@vsavkin is working on a less strong dev mode, which will treat this as OK. But yes, in the strictest sense the pipe should make sure that it returns the same array.

@jhiemer

This comment has been minimized.

Copy link

jhiemer commented Jan 19, 2016

@mhevery that would help a lot. The problem is see is getting an object with properties and returning an array. Normally I would tend to return a new array instead of changing the keys in the existing object. But that's definitely a matter of taste.

@lacolaco

This comment has been minimized.

Copy link
Contributor

lacolaco commented Jan 27, 2016

@mhevery Hi.
I have trouble about a migration from ng-repeat to ngFor.
If so it is the design, I think that a solution for the migration also should be in the design. I wrote a pipe, objectToIterable, for this. But it's hacky.
Shouldn't something for smooth migration be provided by the library?

@gdi2290

This comment has been minimized.

Copy link
Member

gdi2290 commented Jan 27, 2016

I think the current design is fine. What's going on is that ngFor is enforcing the developer to shape the collection correctly.

The problem with ng-repeat was that it allowed for both objects and map which allowed more developers to shoot themselves in the foot (more often when they are concerned with the order). In Angular 1 a lot of "perf" problems were due to developers overloading ng-repeat without considering what is really going on (leaky abstraction). With ng-repeat what you're doing is inverting control of the management of your ViewModel from your controller to the helper directives to compose behavior. You're meant to refactor the behavior into a new directive for complex views such as a grid. With Angular 1, I would say no one took the time to take the next step of refactoring their behavior into components.

So what ngFor is doing, this time around, is enforcing best practices currently discovered in Angular 1 today (of managing the ViewModel in the component (container/controller/smart component) before passing it to the ngFor directive). While this may hurt migration a little bit, it forces developers down the correct path of managing their state correctly. Some developers even go so far as to say if Angular 1 did not include ng-repeat then we wouldn't have any performance problems since developers would have to consider how to manage their collections in a view. This change may also hurt prototyping which was one of Angular 1's strongest strength but someone could also create a ng-repeat/orderBy/filter directive/pipes which has the same API that relied on side effects

Tipe CMS

@austbot

This comment has been minimized.

@colinjlacy

This comment has been minimized.

Copy link

colinjlacy commented Feb 24, 2016

If anyone's interested, I wrote up a blog post following the suggested Pipe solution made by @mhevery: https://webcake.co/object-properties-in-angular-2s-ngfor/

@user414

This comment has been minimized.

Copy link

user414 commented Mar 1, 2016

Ran into this issue, and used the solution posted somewhere since the pipe by @mhevery seem to have the same downside as here
#6392

which is it makes it read only. Instead just used something that was suggested somewhere else, sorry can't find where.

Add something like this in your class

@Input() jsonObject: Object;
...
keys() : Array<string> {
    return Object.keys(this.jsonObject);
}

Do something like this in the template

<template ngFor #currentKey [ngForOf]="keys()" #i="index">
      <div><strong>{{currentKey}}:</strong> {{jsonObject[currentKey]}}</div>
</template>
@IsaacBorrero

This comment has been minimized.

Copy link

IsaacBorrero commented Apr 1, 2016

Just checking if the mapToIterable pipe has been added to ng2? Is there an issue item that I can track to get status updates?

@mhevery

This comment has been minimized.

Copy link
Member

mhevery commented Apr 1, 2016

@IsaacBorrero It has not, would you like to make a PR?

@IsaacBorrero

This comment has been minimized.

Copy link

IsaacBorrero commented Apr 1, 2016

@mhevery I apologize, but what does that mean? Please let me know and I will gladly do it...

@mhevery

This comment has been minimized.

@amcdnl

This comment has been minimized.

Copy link
Contributor

amcdnl commented Apr 13, 2016

@pstephenwille

This comment has been minimized.

Copy link

pstephenwille commented Jul 17, 2016

This seems like a huge glitch. Is it that ordering is inconsistent, in which case, does that matter? Or that looping at all is problematic? At the minimum, I'd expect a default pipe to be available.

@svdb0

This comment has been minimized.

Copy link

svdb0 commented Aug 3, 2016

@mhevery
For what it's worth, as opposed to the order of properties in an object, the order of the elements of a Map has been strictly defined from the first version of ECMAScript which introduced it.

The standard uses a list type to describe it; adding a new element appends it to the end of the list of the key-value pairs, and changing the value of an element leaves the order intact.
See http://www.ecma-international.org/ecma-262/6.0/#sec-map.prototype.set for the Map.prototype.set() function. Other operations use the same list representation.

If there are no other reasons than the perceived inconsistent ordering, considering that iterating through Maps would be very convenient, maybe another look at this could be justified?

@dlinx

This comment has been minimized.

Copy link

dlinx commented Jun 19, 2017

Map have its own keys method. Refer this.

var map = new Map()
map.set(0, 1);
map.keys() // output: 0
@e-cloud

This comment has been minimized.

Copy link

e-cloud commented Jun 20, 2017

@dlinx, ironically, what you've post just proves my thought.

the output of map.keys() is an array. Keyspipe does't get its place.

@trotyl

This comment has been minimized.

Copy link
Contributor

trotyl commented Jun 20, 2017

@e-cloud The output of map.keys() is not an Array, it's an Iterator. And @dlinx just means that there is no need to use a pipe, it's supported by default with proper JavaScript code.

@e-cloud

This comment has been minimized.

Copy link

e-cloud commented Jun 20, 2017

you're right, sorry for my rudeness.

@shral

This comment has been minimized.

Copy link

shral commented Jun 20, 2017

I would expect iterating throw a Map like this:

        <div *ngFor="let key of myMap.keys()">
            key:{{key}} <br/>
            value: {{myMap.get(key)}}
        </div>

and that's working (I'm seeing the list) except that I'm having an "ExpressionChangedAfterItHasBeenCheckedError" error.

Similarly to that, I solved Iterating throw the Object with *ngFor by binding the "Object" to the component:

  @Component(...)
  export class MyComponent {
       Object = Object;
   //The rest of the Code....
   }

Than in my Template:

   <div *ngFor="let key of Object.keys(myObject)">
            key:{{key}} <br/>
            value: {{myObject[key]}}
     </div>

What do you guys think about this solution? I would expect to bee faster than the pipe solution but that need to bee verifyed.

@sk29110

This comment has been minimized.

Copy link

sk29110 commented Aug 31, 2017

Why so complicate to iterate over map? my code seems not working.

component.ts

this.mapValue = new Map<number, string>();
this.mapValue.set(1, 'Some Value 3');
this.mapValue.set(2, 'Some Value 2');

html

<ul>
    <li *ngFor="let key of model.mapValue">
        {{key}}
    </li>
</ul>

result is displaying empty li.

@moravcik

This comment has been minimized.

Copy link

moravcik commented Sep 27, 2017

@mhevery
As it was already said, the original question was about ES6 Map, which is ordered by its specification. You were describing unpredictable ordering of object keys , which is different case.

Is iterating with *ngFor supported for ES6 Maps, or more generally, on objects implementing [Symbol.iterator()] ?

@trumbitta

This comment has been minimized.

Copy link

trumbitta commented Nov 3, 2017

I'm doing it like this:

this.vehicleTypes = vehicleTypesMap; // A map defined elsewhere
this.vehicleTypesKeys = Array.from(this.vehicleTypes.keys());
<select>
    <option *ngFor="let type of vehicleTypesKeys" [value]="type">{{ vehicleTypes.get(type) }}</option>
</select>
@dalu

This comment has been minimized.

Copy link

dalu commented Jan 9, 2018

This is very shortsighted, like so many things in new angular.
That means you're forcing a certain data model on every endpoint, regardless wether it's sensible or not.

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 15, 2018

feat(common): new pipe to support object for ngFor
Add keys_pipe which uses Object.keys in order to transform
an object to array of its keys.

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 15, 2018

feat(common): new pipe to support object for ngFor
Add keys_pipe which uses Object.keys in order to transform
an object to array of its keys.

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 16, 2018

feat(common): new pipe to support object for ngFor
Add keys_pipe which uses Object.keys in order to transform
an object to array of its keys.

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 16, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its [key, value] pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 16, 2018

feat(common): new pipe to support object for ngFor
Add keys_pipe which uses Object.keys in order to transform
an object to array of its keys.

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 19, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 19, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 19, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 19, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 20, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 20, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246

harunurhan added a commit to harunurhan/angular that referenced this issue Feb 21, 2018

feat(common): new pipe to support object for ngFor
Add entries_pipe which uses Object.keys in order to transform
an object to array of its {key, value} pairs

Fix angular#2246
@gordol

This comment has been minimized.

Copy link

gordol commented Mar 23, 2018

this is utterly ridiculous

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment