4
votes

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
http.get returns a Promise. - wannadream
are you able to log this.contacts2 or this.contacts? - Amir
At all costs avoid using setTimeout(), you will not be able to control how quickly the response will resolve and that is exactly why RxJS operators such as map() exist, even just to extract data from nested properties. - Alexander Staroselsky
@AlexanderStaroselsky: Thanks. I also don't intend to use it. hands on stuff. - Jason

2 Answers

6
votes

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

0
votes

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>