95

this is post is about an interesting problem I found at work.

If you don’t know it yet. I’m talking about Angular 2+

The problem

So you want to display the markup for a list, the values for this list come from the back end and for some reason instead of a good old array of objects you receive something like this.

    { 
  "car" : 
    { 
       "color" : "red",
       "model" : "2013"
    },
   "motorcycle": 
    { 
       "color" : "red",
       "model" : "2016"
    },
   "bicycle": 
    { 
       "color" : "red",
       "model" : "2011"
    }
}

Then you try to use *ngFor but a wild error message appears:

Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.

You might fix it in the back end so you get an array of objects, but ain’t no body got time for that. Don’t you worry child, I’ve got us.

Jaime Rios
  • 1,956
  • 2
  • 15
  • 20

9 Answers9

201

In Angular 6.1 the KeyValuePipe was introduced which allows you to iterate object properties:

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

Docs: https://angular.io/api/common/KeyValuePipe

danday74
  • 52,471
  • 49
  • 232
  • 283
  • 1
    fine, but in my case, i am getting second item is rendering as first in UI. any suggestion ? – Krish Oct 13 '18 at 14:16
  • 3
    yes, I have experienced this issue also - have raised a question here to see if anyone has the answer - https://stackoverflow.com/questions/52793944/angular-keyvalue-pipe-sort-properties-iterate-in-order – danday74 Oct 13 '18 at 14:27
  • See @ConnorsFan answer at the link in above comment – danday74 Oct 13 '18 at 15:02
29

I don't know if this is safe, but for these simple cases i don't like the pipe solution, so i usually use:

<div *ngFor="let k of objectKeys(yourObject)">
    {{yourObject[k].color}}
</div>

and in the controller:

objectKeys(obj) {
    return Object.keys(obj);
}

This is a quite frequent case, i don't understand why there isn't a standard implementation for this like in Angular.js 1.x

Michele Federici
  • 1,755
  • 1
  • 14
  • 17
  • This is the best! – Prakhar Londhe Jan 11 '20 at 09:05
  • 9
    do not use functions in the template (except events). It will slow down your app, create a pipe instead. Angular change detection will call this function a lot because, if it wats to know if the value was changed it has to call it so, it's called by change detection loop which results in a slow app. – Pavel B. Jan 12 '20 at 14:10
14

A better solution would be to use a pipe such as this one:

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

/**
 * Convert Object to array of keys.
 */
@Pipe({
  name: 'appProperties'
})
export class PropertiesPipe implements PipeTransform {

  transform(value: {}): string[] {

    if (!value) {
      return [];
    }

    return Object.keys(value);
  }
}

Then in your template:

<div *ngFor="let property of response | appProperties">
    <div *ngFor="let item of response[property]">
         {{item.something}}
    </div>
</div>
EldarGranulo
  • 1,575
  • 1
  • 14
  • 37
12
    <div *ngFor="let item of donation_list | keyvalue">
        <div class="donation-box">
             <Label class="donation-label">Date : {{item.value.PaymentDate}}</Label>
             <Label class="donation-label">Time : {{item.value.PaymentTime}}</Label>

        </div>
   </div>

If you have more properties inside the object, you can use like this.

ChamodyaDias
  • 609
  • 7
  • 8
2

Use Object.values to get a regular array from your object. Object.keys seems like a lot of unnecessary work.

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values

Oleg
  • 594
  • 8
  • 9
2

There are 2 ways:

  1. If your object items don't have key

    { name: 'John', age: 18,... }
    
    <div *ngFor="let item of myObj | keyvalue">
        Key: <strong>{{item.key}}</strong> and Value: <span>{{item.value}}</span>
    </div>
    
  2. If your object items have key

    Pipe

    import { Pipe, PipeTransform } from "@angular/core";
    
    type Args = "keyvalue" | "value" | "key";
    
    @Pipe({
        name: "pipeMapToIterable",
        pure: false
    })
    export class MapToIterablePipe implements PipeTransform {
        transform(obj: {}, arg: Args = "keyvalue") {
            return arg === "keyvalue" ?
                Object.keys(obj).map(key => ({ key: key, value: obj[key] })) :
                arg === "key" ?
                    Object.keys(obj) :
                    arg === "value" ?
                        Object.keys(obj).map(key => obj[key]) :
                        null;
        }
    }
    

    HTML

    { 
        John: { name: 'John', age: 18 },
        Bob: { name: 'Bob', age: 25},
        Marry: { name: 'Marry', age: 22}
    }
    
    <!-- default -->
    <div *ngFor="let item of myObj | pipeMapToIterable">
        Key: <strong>{{item.key}}</strong> and Value: <span>{{item.value}</span>
    </div>
    
    
    <!-- keyvalue -->
    <div *ngFor="let item of myObj | pipeMapToIterable : 'keyvalue'">
        Key: <strong>{{item.key}}</strong> and Value: <span>{{item.value}</span>
    </div>
    
    
    <!-- value -->
    <div *ngFor="let item of myObj | pipeMapToIterable : 'value'">
        Key: <strong>{{item.key}}</strong> and Value: <span>{{item.value}</span>
    </div>
    
    
    <!-- key -->
    <div *ngFor="let item of myObj | pipeMapToIterable : 'key'">
        Key: <strong>{{item.key}}</strong> and Value: <span>{{item.value}</span>
    </div>
    
Kamran Taghaddos
  • 452
  • 1
  • 10
  • 22
0

The solution

In a perfect world, you would get an array of objects, since the world is not always, perfect. What you want to do, is to store all those objects within an array. Here is an over-simplified solution in plain old JavaScript.

Step 1. Get all the object keys. using Object.keys. This method returns an array of a given object’s own enumerable properties.

Step 2. Create an empty array. This is an where all the properties are going to live, since your new ngFor loop is going to point to this array, we gotta catch them all.

Step 3. Iterate throw all keys, and push each one into the array you created.

Here’s how that looks like in code.

// Evil response in a variable. Here are all my vehicles.
let evilResponse = { 
  "car" : 
    { 
       "color" : "red",
       "model" : "2013"
    },
   "motorcycle": 
    { 
       "color" : "red",
       "model" : "2016"
    },
   "bicycle": 
    { 
       "color" : "red",
       "model" : "2011"
    }
}
// Step 1. Get all the object keys.
let evilResponseProps = Object.keys(evilResponse);
// Step 2. Create an empty array.
let goodResponse = [];
// Step 3. Iterate throw all keys.
for (prop of evilResponseProps) { 
    goodResponse.push(evilResponseProps[prop]);
}

Then you can assign the goodResponse to the class property you were trying to iterare trough in the first place.

That’s all folks.

Jaime Rios
  • 1,956
  • 2
  • 15
  • 20
-1

You can try this:

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

For more info visit: https://angular.io/api/common/KeyValuePipe

JW Geertsma
  • 857
  • 3
  • 13
  • 19
-1

declare your array as any and then assign your object

data=[] as any;
myObj={};

data=myObj;

now loop through data array

<tr *ngFor="let item of data">
<td>{{item.property}}</td>
</tr>
ilyas
  • 27
  • 4