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

Viewed 10902

I have a component loading a local json file (contains only name) from assets folder.Since HttpClient takes care of formatting data as json, so didn't use map here.

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'

 export type cont= {name: string};

@Component({
  selector: 'my-app',
  template: `
    1st List
    <ul>
      <li *ngFor="let contact of contacts | async">{{contact.name}}</li>
    </ul>
    2st List
    <ul>
      <li *ngFor="let contact of contacts2 | async">{{contact.name}}</li>
    </ul>
    `
})
export class AppComponent {;
  contacts: Observable<cont[]>;
  contacts2: Observable<cont[]>;
  constructor (http: HttpClient) {
    this.contacts = http.get<cont[]>('http://localhost:4200/assets/contacts.json');               
    setTimeout(() => this.contacts2 = this.contacts, 500);
  }
}

Contacts.json

 {
"items": [
  { "name": "Mark Twain" },
  { "name": "Sherlock Holmes" }
]

}

Data can be fetched on browser via http://localhost:4200/assets/contacts.json but When I tried to render the same via async pipe I get this error below.Tried many ways to convert observable to Observable<[]> but none passed this scenario. What's wrong here. how json data over httpclient can be converted to Array ? (tried to follow similar posts but none helped)?

Error

2 Answers

You are trying to iterate over an object:

{
  "items": [
    { "name": "Mark Twain" },
    { "name": "Sherlock Holmes" }
  ]
}

You should iterate over contacts.items

<ul>
  <li *ngFor="let contact of (contacts | async)?.items">{{contact.name}}</li>
</ul>

or just use observable map:

const url = 'http://localhost:4200/assets/contacts.json';
this.contacts = http.get<cont[]>(url).pipe(map(data => data.items)); 

ANOTHER SOLUTION using Symbol.iterator

I am adding it just because it is interesting (in my opinion) approach although not practical in this case.

I can leave iteration over an asynchronous object:

<ul>
  <li *ngFor="let contact of contacts | async">{{contact.name}}</li>
</ul>

but I will convert this object to iterable by adding iterable protocol:

const url = 'http://localhost:4200/assets/contacts.json';
this.contacts = http.get<cont[]>(url).pipe(
  map((data: any) => {
    data[Symbol.iterator] = () => data.items[Symbol.iterator]();
    return data;
  })
);

The line

data[Symbol.iterator] = () => data.items[Symbol.iterator]()

redirects iteration over the object (which is impossible by default) to iteration over its items array.

I created a STACKBLITZ demonstrating this

this will surely work just define varible correctly

this happens when data json you have in the variable is not in the array format.define variable like this

 contacts:[] and store the json from api like this:-
    response = http.get<cont[]>('http://localhost:4200/assets/contacts.json'); 
    this.contacts= response.items //array start from items key

    <ul>
      <li *ngFor="let contact of contacts">{{contact.name}}</li>
    </ul>
Related