Need help with Http service: NgFor only supports binding to Iterables such as Arrays."


#1

Dear Fellow Ionites

I’m trying to understand how to setup an HTTP data fetch service. I think I have done everything correctly but still I get an error

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

I have seen responses in similar problems, to mine but failed to understand what goes wrong with my code.
Let me explain:

I play around fetching JSON array from this link
http://jsonplaceholder.typicode.com/posts

If you put in browser you will see that it returns directly an array.

My code:
data-service.ts

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import {Observable} from 'rxjs/Observable';

@Injectable()

export class DataService {
    
    constructor (
        private _http:Http
    ){}
    
    getPosts():Observable<any>{
        return this._http.get('http://jsonplaceholder.typicode.com/posts')
            .map(res => res.json());
    }
    
}

page1.html
<ion-navbar *navbar>
Tab 1

<ion-content padding class="page1">

  <button (click)="getData()">Get Data</button>
  <p>{{data}}</p>
  <p *ngFor="let i of data">
    {{i.id}}
  </p>
</ion-content>

page1.ts

import {Page} from 'ionic-angular';
import {DataService} from '../../services/data-service/data-service';


@Page({
  templateUrl: 'build/pages/page1/page1.html',
  providers: [DataService]
})

export class Page1 {

  data:any;
  
  constructor(
    private _data: DataService
  ) {

  }
  
  getData(){
    this.data=this._data.getPosts()
      .subscribe (
        response => {
            this.data=response;
            console.log(this.data);
          },
        error => console.log ('error in download')
      )
    }
}

When I remove the *ngFor part from the HTML, and click on GetData the data are fetched. For a split second I see in my browser displayed [object Object], and then an array of [object Object]

If I put *ngFor part in my HTML, then I get the error:

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

I’m under the impression that the page does not see the returned data as array and thus ngFor throws the error. But in any case the console.log command shows that my data are indeed an Array of objects each time.

So I’m a bit confused. The data I get is an array (of course it’s a JSON but then it is converted in an array of objects). Yet my ngFor fails to work and throws an error.

Can some explain please? I’m totally confused. All these were based on tutorials I have found here and there, and yet they don’t seem to work. So I’m missing the best practice here.

Many thanks in advance.

Killerchip


#2

@killerchip I haven’t tested this, but after a quick look at your code I noticed that you’re assigning twice to this.data. I’m not sure if this would solve the problem, but this is the first thing that I’ll fix, namely to remove this assignment this.data=this._data.getPosts() and set the this.data only in the callback. Could you try this and confirm if the problem is fixed or not?

  getData() {
    // TODO: Don't assign to this.data here:
    this.data=this._data.getPosts()
      .subscribe (
        response => {
            // Assign to this.data only here:
            this.data=response;
            console.log(this.data);
          },
        error => console.log ('error in download')
      )
    }
  }

#3

YES!!! It worked.

But still confused on what I did wrong.

Is this the ‘best practice’ to do it?

Many thanks again :slight_smile:


#4

Well, if the suggested fix worked, this means that you were actually assigning the result of the subscribe() method to this.data instead of the response variable/parameter which contained the results. As this is an asynchronous example, it’s not clear which of both values would be assigned to this.data because it depends on the order in which the assignments will be executed (which is not guaranteed in async JS code). Therefore it’s theoretically possible to work in one case and to fail in another which might result in a hard to find bug. I would recommend you to avoid reusing variables and especially class properties. In the particular case if you really wanted to keep the subscription you could use a local variable for this.


#5

Yep. It make sense:
The fist time this.data gets the result of .subscribe method. THus I see [object object] as the result of {{data}} interpolation.
But when ngFor tries to render it, of course fails.

After a while the JSON comes from the network and this.data gets the response. Thus {{data}} now gives the array of objects. But if ngFor is used, then it already has failed on the first time… and thus the error.

Many thanks again for the help !!

Killerchip


#6

In addition to @iignatov’s great advice, I would recommend attempting to minimize your use of any. Generally about 95% of the time I see any, it’s code for “programmer too lazy to figure out or define a proper type”. This is a perfect situation where if data was typed properly, you would have caught this error at build time, because the two assignments to data are of different types: Observable<Foo> and Foo. If data was typed as Foo, the first assignment would have been flagged as erroneous (properly) by tsc.