2

I'm trying to create a list of data returned from an endpoint, i get 10 bits of data back and i want to use *ngFor to display them. I already have the data coming in correctly at the right time but it's saying

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

However from what I've seen you can use json in *ngFor in recent versions of Angular.

JSON returned: https://pastebin.com/TTn0EqSS

app.component.html

<div [class.app-dark-theme]="true">
    <mat-sidenav-container fullscreen class="sidenav-container">
        <mat-toolbar class="toolbar">
            Coin Market Cap 3rd party api app
        </mat-toolbar>

        <mat-card>
            <mat-card-header>
                <mat-card-title>CryptoCurrency Market Overview</mat-card-title>
                <mat-card-subtitle>Top 15 current currencies.</mat-card-subtitle>
            </mat-card-header>

            <mat-card-content class="currency-listings">
                <div *ngIf="finishedLoading">
                    <mat-grid-list cols="1" rowHeight="2:1">
                        <mat-grid-tile *ngFor="let currency of currenciesJson; let i = index" (click)="selectCurrency(i)"> 
                            {{currency.data[i].name}}
                        </mat-grid-tile>
                    </mat-grid-list>

                    test 
                    test
                    test
                </div>
            </mat-card-content>
        </mat-card>

        <!--    (click)="showInfo(true)"   -->

        <mat-card *ngIf="displayInfo">
            test
        </mat-card>
    </mat-sidenav-container>
</div>

coin-market-cap.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class CoinMarketCapService 
{   
    key = "REDACTED";   
    apiUrl = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/';

    constructor(public http: HttpClient) { }

    getCurrencies(totalCurrencies: number)
    {
        let promise = new Promise((resolve, reject) => {
            let url = this.apiUrl + "listings/latest?limit=" + totalCurrencies + "&CMC_PRO_API_KEY=" + this.key;
            this.http.get(url)
            .toPromise()
            .then(
            res => { 
                console.log(res);
                resolve();
            });
        })
        return promise;
    }

    getCurrency(currencyId: number)
    {
        console.log("in getcurrency");
        let url = this.apiUrl + "info?id=" + currencyId + "&CMC_PRO_API_KEY=" + this.key;
        console.log(url);
        return this.http.get(url);
    }
}

app.component.ts

import { Component } from '@angular/core';
import { CoinMarketCapService } from '../services/coin-market-cap.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent 
{   
    currenciesJson = {};
    displayInfo = false;
    finishedLoading = false;

    constructor(private CoinMarketCapService: CoinMarketCapService) {}

    ngOnInit()
    {
        console.log(this.currenciesJson);
        this.currenciesJson = this.CoinMarketCapService.getCurrencies(10)
        .then(res => 
            this.finishedLoading = true
        )
        console.log(this.currenciesJson);
        console.log("exiting ngoninit");
    }

    selectCurrency(currencyId: number)
    {
        console.log(currencyId);
        let currencyObject = this.CoinMarketCapService.getCurrency(currencyId);
    }

    showInfo ( showInfo: boolean )
    {
        this.displayInfo = showInfo;
    }
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Daniel Turcich
  • 1,764
  • 2
  • 26
  • 48
  • https://stackoverflow.com/questions/51732524/how-to-iterate-json-properties-in-typescript/51732597?noredirect=1#comment90426226_51732597 – Chellappan வ Aug 07 '18 at 18:10

6 Answers6

3

In Angular 6.1.something (very recently released) there is a pipe for iterating JSON objects.

Its call the KeyValuePipe - Docs are here - https://angular.io/api/common/KeyValuePipe

<div *ngFor="let item of object | keyvalue">
  {{item.key}}:{{item.value}}
</div>

This is so new that you must be on the very latest version of Angular 6. If you are already on Angular 6 then just run:

ng update @angular/core
ng update @angular/cli

To get the latest.

Here's an article on it - http://www.talkingdotnet.com/angular-6-1-introduces-new-keyvalue-pipe/

danday74
  • 52,471
  • 49
  • 232
  • 283
  • Do you have an example of how i can implement this into my application or the documentation for it? – Daniel Turcich Aug 07 '18 at 20:26
  • I used it last night and it works great but I changed my code in the end. I'll find it for you and update my answer - will take a few mins – danday74 Aug 07 '18 at 20:27
  • github.com/buffet-time/coinMarketCapApiWebApp (key pro.coinmarketcap.com free tier available) here's my current repo, for some reason it's not assigning the data im correctly getting from the GET request to the value im trying to assign it to which is currenciesJson. If i was able to get that then I assume i'd be able to use this feature. If you know what the issue i'd really appreciate the help. – Daniel Turcich Aug 07 '18 at 20:38
  • my guess is that you have not changed the object reference. This signals to angular that a change has taken place and change detection kicks in, updating the DOM. One easy way to get a new reference is use cloneDeep from lodash ... obj = _.cloneDeep(obj) ... or for very simple objects ... obj = {...obj} – danday74 Aug 07 '18 at 20:42
0

ngFor iterates through arrays. But read your code:

currenciesJson = {};

Here you intialize it to an object.

this.currenciesJson = this.CoinMarketCapService.getCurrencies(10)
    .then(res => 
        this.finishedLoading = true
    )

And here you initialize it to a promise.

So that can't possibly work. It must be an array.

I stronly suggest you read the guide about HTTP to know how correctly use HttpClient to get data. You shouldn't use promises, especially the way you're using them, which is not correct as all, and full of antipatterns. It should boild down to:

getCurrencies(totalCurrencies: number): Observable<Array<Currency>> {
    const url = this.apiUrl + "listings/latest?limit=" + totalCurrencies + "&CMC_PRO_API_KEY=" + this.key;
    return this.http.get<Array<Currency>>(url);
}

and, in the component:

currenciesJson: Array<Currency> = [];
...

this.CoinMarketCapService.getCurrencies(10).subscribe(currencies => this.currenciesJson = currencies);

Note how specifying types allows the compiler to tell you everything that is wrong in your code.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I changed the component and service to what you have, however how do with an observable execute something synchronously afterwards so i can start the *ngFor and is there anything that needs to change in the *ngFor for that to work correctly with your implementation? – Daniel Turcich Aug 07 '18 at 18:32
  • I also neglected to mention that TS rejects the Array everywhere it's used so i can't do it that way. – Daniel Turcich Aug 07 '18 at 19:40
  • You of course need to define that Currency type. Since your variable is named currenciesJson, I assumed a good name for the type is Currency. But you need to define it, to specify what a Currency is. This is Typescript fundamentals. Take a big step back, and take some time to learn what a type is, what an interface is, what an object is, what an array is. – JB Nizet Aug 07 '18 at 20:12
  • What exactly is the purpose of doing this when from the other answers and what i've seen Angular 6.1 allows you to go into the data array in the json object? – Daniel Turcich Aug 07 '18 at 20:14
  • I know what a type, an interface, what an object is etc. I'm new to angular/ typescript. it's not exactly structured like C#/ Java. But still i don't see the purpose in doing it this way when from what i say it can be done the way i'm trying to do it is all im saying. – Daniel Turcich Aug 07 '18 at 20:21
  • It allows you, and the persons who will read your code later to maintain it, and the compiler, to understand what kind of data you're manipulating. What can you say about these variables: `let a; let b;`. What do they represent? Pretty hard, no? Now what can you say about these ones: `let amount: number; let currency: Currency;` Isn't that easier to understand? Regarding "the way you're trying to do it". Well, you asked a question about it, because it doesn't do what it should, causes an error to be thrown, and takes 4 times the number of lines of code of "my way". – JB Nizet Aug 07 '18 at 20:24
0

first avoid the initialisation:
put currenciesJson: object; instead of currenciesJson = {};
And change the ngOnInit method to:

ngOnInit()
    {
        this.CoinMarketCapService.getCurrencies(10)
        .then(res => {
            this.currenciesJson = res
            this.finishedLoading = true
        });
    }

EDIT

<mat-grid-tile *ngFor="let currency of currenciesJson.data; let i = index" (click)="selectCurrency(i)"> 
       {{currency.name}}
</mat-grid-tile>
K. Ayoub
  • 406
  • 1
  • 5
  • 15
  • It correctly returns the data however i cant find a way to display it with the *ngFor now – Daniel Turcich Aug 07 '18 at 19:27
  • With what i currently have the res is correctly being logged in console as having gotten the data, however it doesn't seem to be correctly being assigned to the currenciesJson variable. github.com/buffet-time/coinMarketCapApiWebApp here is a git repo of the app with the API key redacted (pro.coinmarketcap.com) 6000 free requests a month for the free tier – Daniel Turcich Aug 07 '18 at 19:42
0

The easiest way to fix this is to create an TypeScript model object to map the incoming json file to.

For example, use your own keys from the Json object:

export class CurrencyModel {
    currency: string;
    foo: number;
    bar: number;
    constructor(currency: string, foo: number, bar: number) {
        this.currency = currency;
        this.foo = foo;
        this.bar = bar;
    }

}

then you would create a new instance of the object model with:

currencyData: CurrencyModel;
getCurrency(currencyId: number)
{
    console.log("in getcurrency");
    let url = this.apiUrl + "info?id=" + currencyId + "&CMC_PRO_API_KEY=" + this.key;
    console.log(url);
    response = this.http.get(url);
    response.subscribe(data => {
        this.currencyData = data;
    }, error1 => {
        console.log(error1);
    })
}
Josh Harkema
  • 250
  • 3
  • 11
0

*ngFor is only used to iterate over Array, not Objects, your response contains an object with status(Object) and data(array of objects). You can loop over array of object like this.

this.currenciesJson = {
  status : {},
  data: [{}]
}

You can only loop over currenciesJson.data

Try setting

<mat-grid-tile *ngFor="let currency of currenciesJson.data; let i = index" (click)="selectCurrency(currency)"> 
   {{currency.name}}
 </mat-grid-tile>

Moreover, You can loop over object keys by keyValue pipr if using angular 6.1 or by using Object.keys() if using angular 5 , but i think your problem is to loop over 'data' instead of 'keys'

Danish Arora
  • 330
  • 1
  • 6
  • How exactly would i implement this into my current application? I'm willing to do reading later more up on how exactly it works but i preferably would like to resolve the issue first. – Daniel Turcich Aug 07 '18 at 19:23
  • try doing it his way ... {{currency.name}} – Danish Arora Aug 07 '18 at 19:29
  • Try the approach i have suggested that will solve your problem or share a stackblitz i can help !!. – Danish Arora Aug 07 '18 at 19:30
  • https://github.com/buffet-time/coinMarketCapApiWebApp here is a git repo of the app with the API key redacted (https://pro.coinmarketcap.com/) 6000 free requests a month for the free tier – Daniel Turcich Aug 07 '18 at 19:35
  • Yes, I have through the code, Try doing it in above way, it should work for you – Danish Arora Aug 07 '18 at 19:38
  • Please tell me when your do console.log(this.currenciesJson), you are getting same response as shown in https://pastebin.com/TTn0EqSS, if yes, loop over its data key – Danish Arora Aug 07 '18 at 19:39
  • it returns undefined. its not assigning the currenciesJson variable to the data im getting. Which i know is coming in because the console shows the data is coming into the get request but its not being assigned correctly – Daniel Turcich Aug 07 '18 at 19:58
0

From help from the comments/ answers in this post I got to the point which I wanted.

Angular 6.1s new Key Value Pipe was used here. https://angular.io/api/common/KeyValuePipe

It's very hacky but for what it was for it's fine. If I were to come back to this i'd definitely improve a lot but it works.

Here's the source for the 3 files.

app.component.ts

import { Component } from '@angular/core';
import { CoinMarketCapService } from '../services/coin-market-cap.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent 
{   
    currenciesJson: object;
    selectedCurrency: object;
    displayInfo: boolean = false;
    finishedLoading: boolean = false;

    constructor(private CoinMarketCapService: CoinMarketCapService) {}

    ngOnInit()
    {
        this.CoinMarketCapService.getCurrencies(15)
        .then(res => {
            this.currenciesJson = res,
            this.finishedLoading = true;
        });
    }

    selectCurrency(currencyId: number)
    {
        this.CoinMarketCapService.getCurrency(currencyId)
        .then( res => {
            this.selectedCurrency = res,
            this.showInfo(true)
        })
    }

    showInfo ( showInfo: boolean )
    {
        this.displayInfo = showInfo;
    }
}

coin-market-cap.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class CoinMarketCapService 
{   
    key: string = "REDACTED"; // https://pro.coinmarketcap.com/ free 6000 requests per month.   
    apiUrl: string = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/';

    constructor(public http: HttpClient) { }

    getCurrencies(totalCurrencies: number)
    {
        let promise = new Promise((resolve, reject) => {
            let url: string = this.apiUrl + "listings/latest?sort=market_cap&cryptocurrency_type=coins&limit=" + totalCurrencies + "&CMC_PRO_API_KEY=" + this.key;
            this.http.get(url)
            .toPromise()
            .then(
            res => { 
                resolve(res);
            });
        })
        return promise;
    }

    getCurrency(currencyId: number)
    {
        let promise = new Promise((resolve, reject) => {
            let url: string = this.apiUrl + "info?id=" + currencyId + "&CMC_PRO_API_KEY=" + this.key;
            this.http.get(url)
            .toPromise()
            .then(
            res => { 
                resolve(res);
            });
        })
        return promise;
    }
}

app.component.html

<div [class.app-dark-theme]="true">
    <mat-sidenav-container fullscreen class="sidenav-container">
        <mat-toolbar class="toolbar">
            Coin Market Cap 3rd party api app
        </mat-toolbar>

        <mat-card>
            <mat-card-header>
                <mat-card-title>CryptoCurrency Market Overview</mat-card-title>
                <mat-card-subtitle>Top 15 current currencies.</mat-card-subtitle>
            </mat-card-header>

            <mat-card-content>
                <div *ngIf="finishedLoading">
                    <mat-grid-list cols="1" rowHeight="40px">
                        <mat-grid-tile *ngFor="let currency of currenciesJson.data | keyvalue; let i = index" 
                                (click)="selectCurrency(currency.value.id)" class="grid-tile"> 
                            {{currency.value.name}}
                        </mat-grid-tile>
                    </mat-grid-list>
                </div>
            </mat-card-content>
        </mat-card>

        <mat-card *ngIf="displayInfo">  
            <mat-card-header>
                <mat-card-title>Selected Cryptocurrency Details</mat-card-title>
                <mat-card-subtitle>Name and links of selected currency</mat-card-subtitle>
            </mat-card-header>

            <mat-card-content *ngFor="let currency of selectedCurrency.data | keyvalue; let i = index">
                Name: {{currency.value.name}} <br><br>
                Ticker Symbol: {{currency.value.symbol}}<br><br>
                Website: {{currency.value.urls.website}}<br><br>
                Source Code: {{currency.value.urls.source_code}}<br><br>
                Twitter: {{currency.value.urls.twitter}}
            </mat-card-content>
        </mat-card>
    </mat-sidenav-container>
</div>
Daniel Turcich
  • 1,764
  • 2
  • 26
  • 48