168

I am trying to do some things in Angular 2 Alpha 28, and am having an issue with dictionaries and ngFor.

I have an interface in TypeScript looking like this:

interface Dictionary {
    [index: string]: string
}

In JavaScript this will translate to an object that with data might look like this:

myDict={'key1':'value1','key2':'value2'}

I want to iterate over this and tried this:

<div *ngFor="(#key, #value) of myDict">{{key}}:{{value}}</div>

But to no avail, none of the below worked either:

<div *ngFor="#value of myDict">{{value}}</div>
<div *ngFor="#value of myDict #key=index">{{key}}:{{value}}</div>

In all cases I get errors like Unexpected token or Cannot find 'iterableDiff' pipe supporting object

What am I missing here? Is this not possible anymore? (The first syntax works in Angular 1.x) or is the syntax different for iterating over an object?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Rickard Staaf
  • 2,650
  • 2
  • 14
  • 14

18 Answers18

253

Angular 6.1.0+ Answer

Use the built-in keyvalue-pipe like this:

<div *ngFor="let item of myObject | keyvalue">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

or like this:

<div *ngFor="let item of myObject | keyvalue:mySortingFunction">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

where mySortingFunction is in your .ts file, for example:

mySortingFunction = (a, b) => {
  return a.key > b.key ? -1 : 1;
}

Stackblitz: https://stackblitz.com/edit/angular-iterate-key-value

You won't need to register this in any module, since Angular pipes work out of the box in any template.

It also works for Javascript-Maps.

bersling
  • 17,851
  • 9
  • 60
  • 74
  • You should add `implements PipeTransform` in the class definition (see https://angular.io/guide/pipes#custom-pipes) – toioski Mar 09 '18 at 16:39
  • 1
    @toioski thanks, i've added it and updated to the new syntax of the for loop. – bersling Mar 09 '18 at 17:29
  • 1
    Great answer, used this to ngFor my Dictionary. I had to do keyValuePair.val[0] though as my values ended up [{}] and not {} – jhhoff02 Jun 04 '18 at 19:13
  • 1
    Is there an advantage to this over just `return Object.keys(dict).map(key => ({key, val: dict[key]}))`? – Justin Morgan - On strike Sep 14 '18 at 15:28
  • I don't see any, actually I'd use your way! – bersling Sep 14 '18 at 15:45
  • note that you need to use at least angular 6.1.10 for the Angular 6+ answer, Tried it on 6.0.9 before and it did not work. – LuckyLikey Oct 19 '18 at 09:38
  • @LuckyLikey Thanks for spotting this. I fixed the answer. – bersling Oct 19 '18 at 12:03
  • When I see the error message in angular 7, there's no hint that this pipe exists. Could you please modify your answer to include "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays." or something? – activedecay Jan 18 '19 at 17:53
  • This worked for me! you just need to implement " keyvalue pipe " into *ngFor loop. – TheScripterX Sep 25 '21 at 11:48
94

It appears they do not want to support the syntax from ng1.

According to Miško Hevery (reference):

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 *ngFor"var item of map | mapToIterable">

So in order to iterate over your object you will need to use a "pipe". Currently there is no pipe implemented that does that.

As a workaround, here is a small example that iterates over the keys:

Component:

import {Component} from 'angular2/core';

@Component({
  selector: 'component',
  templateUrl: `
       <ul>
       <li *ngFor="#key of keys();">{{key}}:{{myDict[key]}}</li>
       </ul>
  `
})
export class Home {
  myDict : Dictionary;
  constructor() {
    this.myDict = {'key1':'value1','key2':'value2'};
  }

  keys() : Array<string> {
    return Object.keys(this.myDict);
  }
}

interface Dictionary {
    [ index: string ]: string
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Jesse Good
  • 50,901
  • 14
  • 124
  • 166
72

try to use this pipe

import { Pipe, PipeTransform } from '@angular/core';

@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 *ngFor="#value of object | values"> </div>
aloisdg
  • 22,270
  • 6
  • 85
  • 105
obscur
  • 721
  • 5
  • 2
  • 5
    Brilliant, and if I want to keep the reference to the key I will just map an object with both key and value instead. I wish I could mark several answers as accepted answer, since this is the solution to my problem whilst the marked answer is the answer to my question. – Rickard Staaf Dec 05 '15 at 07:03
  • 1
    @obscur - If I do the above now, I get an error "expression has changed after it was checked" using angular2.beta.0.0. Any thoughts? – user2294382 Feb 01 '16 at 23:13
  • Thats because pure: false requires a change detectionStrategy to be injected – Judson Terrell May 05 '16 at 04:13
  • 1
    Why setting it to impure? – tom10271 Dec 08 '16 at 09:22
  • This worked well for me. Only thing was I could not use # in the ngFor. Used let instead. – MartinJH Sep 10 '18 at 09:26
  • use object.values() insted of using own mapping example: return Object.values(value); – dinuka saminda Dec 18 '18 at 17:46
32

Updated : Angular is now providing the pipe for looping through a json Object via keyvalue :

<div *ngFor="let item of myDict | keyvalue">
  {{item.key}}:{{item.value}}
</div>

WORKING DEMO , and for more detail Read


Previously (For Older Version) : Till now the best / shortest answer I found is ( Without any Pipe Filter or Custom function from Component Side )

Component side :

objectKeys = Object.keys;

Template side :

<div *ngFor='let key of objectKeys(jsonObj)'>
   Key: {{key}}

    <div *ngFor='let obj of jsonObj[key]'>
        {{ obj.title }}
        {{ obj.desc }}
    </div>

</div>

WORKING DEMO

peace
  • 13
  • 1
  • 4
Vivek Doshi
  • 56,649
  • 12
  • 110
  • 122
20

In addition to @obscur's answer, here is an example of how you can access both the key and value from the @View.

Pipe:

@Pipe({
   name: 'keyValueFilter'
})

export class keyValueFilterPipe {
    transform(value: any, args: any[] = null): any {

        return Object.keys(value).map(function(key) {
            let pair = {};
            let k = 'key';
            let v = 'value'


            pair[k] = key;
            pair[v] = value[key];

            return pair;
        });
    }

}

View:

<li *ngFor="let u of myObject | 
keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>

So if the object were to look like:

myObject = {
    Daario: Naharis,
    Victarion: Greyjoy,
    Quentyn: Ball
}

The generated outcome would be:

First name: Daario
Last Name: Naharis

First name: Victarion
Last Name: Greyjoy

First name: Quentyn
Last Name: Ball

SimonHawesome
  • 1,292
  • 2
  • 14
  • 19
  • 2
    just one thing to mention you need to change the View: as `
  • First Name: {{u.key}}
    Last Name: {{u.value}}
  • `. +1 from me. – sib10 Jul 27 '17 at 19:04
  • The code inside your map function could be simplified as: `return { 'key' : key, 'value' : value[key] };` – Makotosan Apr 09 '19 at 13:33