[Solved] ngFor with object type (json)


#1

Hello,

I’m trying to import an external json feed and save it into ionic native storage so it is readable offline.

My TS file look like this…

export class HomePage {
  list: any;
  listlocal: any;
  constructor(private sqlitePorter: SQLitePorter, private sqlite: SQLite, private storage: Storage, private iab: InAppBrowser, public navCtrl: NavController, private auth: AuthProvider, public navParams: NavParams) {
    this.fetch();
    this.listlocal = this.storage.get('list'); //Attempting to retrieve them... Not working.
  }

[...]

// this.auth.MaCampagne is calling an external HTTP provider.
// MaCampagne (campagne_id, userid) {
//  return this.http.get('http://url.com/json.php?id='+ campagne_id +'&user=' + userid).map(res => res.json());
// }

  fetch() {
    this.auth.MaCampagne(this.navParams.get('campagne_id'), this.navParams.get('userid')).subscribe(
      result => {
        this.list=result.data.inserts; //Fetching the json, iteration is working here. Defined as array.
          this.storage.set('list', result.data.inserts); //Storing values
      },
      err =>{
        console.dir(err);   
  	  },
    );
  }

Within my HTML file, my list header is like…
<div *ngFor="let item of listlocal; let i = index;">

If I replace “listlocal” (offline list) with my “list” (online list) it is working perfectly. However, if I use the values I stored, it says i can only use ngFor on arrays. I went on Google about it, most solutions I found said to use “keys” pipe, but I did not manage to make any of them work.

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

I’ve been struggling with that for long now. I don’t get why my online list is considered an array and the other one is an object. I need to convert it into an array. How can I accomplish that?

03%20PM

EDIT : I just edited my code. I meant to type this.listlocal=this.storage.get, not storage.set.


#2

Replace all instances of any in your code with a proper type, and explicitly declare return value types for every method you write. Following this process should make it crystal clear what is happening and why.


#3

Is storage.set really returning the list?


#4

this.storage.set returns a promise, which is what will end up assigned to this.listlocal.

I’m also not clear on the logic of what you’re trying to do. It seems like the steps would be:

  • try fetching the fresh data from the api
    • if that’s successful, store the results in a variable, and store the results in local storage
    • if it’s not successful, get the previously saved results from local storage

Maybe the error handler of your subscription to the api call is a good place for that


#5

Hey! Thank you for trying to help everyone. I just noticed I have done a mistake. I meant to make this.listlocal equal to storage.get and not storage.set. I just edited my first post so it’s reflecting better what I’m trying.

I can’t believe I’m still struggling with this issue…


#6

The data fetched from the API is successfully stored into native storage. That’s what I’m trying to do : get the previously saved results from local storage…


#7

Sorry for the mistake between get and set. This being said this.listlocal = this.storage.get(‘list’) into a console log will return the following…

The array is there within __zone_symbol__value… but I just don’t know how to reach it.


#8

Any chance you’ll eventually get around to doing what I suggested back when this thread started?


#10

I can manage to display the list from the API with “list” but not from native storage with “listlocal”. « list: any; » is not causing me any issue. That’s why I don’t understand how « listlocal: any » wouldn’t do the same. I did try any other type I could figure out. But it did not work.


#11

I guess I’ll take that as a “no”.

If you would, though, then tsc will start telling you exactly which of your expectations are wrong, and in what fashion. You would be able to figure this out (and all further similar situations you run into) entirely by yourself.


#12

Explain better? Perhaps I just don’t understand what you mean. You’re telling me to change “any” for another type. I did. You’re also telling me to use return within my method. I’m doing so already with this.auth.MaCampagne I think?


#13

Its still a promise not an array
you have to use await or a .then( data => {}) callback


#14

listlocal still appears to be any in your OP. That means tsc cannot flag situations where you assign something to it that isn’t what you expect, which is precisely what is happening. As @bwhiting explained to you earlier, interactions with storage return futures. They do not return underlying concrete data. If you gave listlocal a proper type like Foo[], you would get a very clear error message (instantly from your IDE, if it’s any good) telling you that Promise<any> (which is what storage.get() is returning) is not assignable to Foo[].

No, I said:

That means fetch(): something instead of just fetch(), and if that something is void, then you cannot do anything from outside of fetch() that cares about when fetch() has achieved what. In this case, we emphatically do care, because you’re going to try to read from storage before fetch() has written to it. So fetch() has to return a future of some sort, and once you declare it that way, you’ll have to actually do so, instead of having the black hole that exists currently.

What I would really do is move all the interaction with HTTP and storage into a service provider and get all of that stuff out of page components. How you sort out when to read from the network and when to read from storage can be done in various ways, but the overall structure should look more like this:

@Injectable() export class CampagneService {
  constructor(private _http: HttpClient, private _storage: Storage) {}

  campagne(uid: string, cid: string): Observable<Campagne> {...}
}

@Component() export class Page {
  campagne: Campagne;

  constructor(np: NavParams, svc: CampagneService) {
    svc.campagne(np.get('userid'), np.get('campagne_id'))
      .subscribe(c => this.campagne = c);
  }

The page should neither know nor care how, when, or from where the data is retrieved. That allows you to (a) modify that strategy easily, without touching any page component code, and (b) leverage all this in any page you wish without additional boilerplate.


#15

Success!!! :slight_smile: Thank you for explaining that to me, it worked! . The .then part was a very good clue and afterwise the property part became quite obvious. I’m very new to TypeScript, I didn’t even know Javascript before I started, therefore I miss lot of parts. In case someone somewhere coming from Google has the same issue, here’s how I twisted my code :

listlocal: Array<any>;

[...]

this.storage.get('list').then( 
      data => {
          this.listlocal = data;
          console.log(this.listlocal); 
        });