4

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

4
  • http.get returns a Promise. Commented Jan 4, 2019 at 19:10
  • are you able to log this.contacts2 or this.contacts? Commented Jan 4, 2019 at 19:13
  • 1
    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. Commented Jan 4, 2019 at 19:20
  • @AlexanderStaroselsky: Thanks. I also don't intend to use it. hands on stuff. Commented Jan 4, 2019 at 19:29

2 Answers 2

7

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

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks !!. Could you elaborate it a bit http.get<{[key:string]: cont[]}>(url).pipe(map(data => data.items)); please ?
Since I did not see any interface or type for your object, I just said that the output of your get request will be an object with any string key ('items' in your case). But actually, after the map we will return an array only, so I will update my answer and return your original type
tried it out, data is being logged both ways and rendered too. but with let contact of (contacts | async).items">{{contact.name}}' I get **ERROR TypeError: Cannot read property 'items' of null`** at browser though data is rendered.
just add safe null operator ? (angular.io/guide/…): <li *ngFor="let contact of (contacts | async)?.items">{{contact.name}}</li>. I updated my answer as well.
I added additional solution using Symbol.iterator, Please check my updated answer
0

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>

1 Comment

response in your example will be an observable returned by http which does not have items property. Use rxjs's map function to accomplish this. Please see my answer above regarding map function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.