Difficulty encountered while attempting to iterate through a JSON object using *ngFor

Attempting to compile a list of data fetched from an endpoint, I receive 10 pieces of data and aim to utilize *ngFor to exhibit them. The data is successfully received in the correct order, but an error arises:

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

However, it seems that utilizing JSON in *ngFor is possible with recent Angular versions.

JSON received: 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;
    }
}

Answer №1

Recently in the latest version of Angular 6.1, a new pipe has been introduced for iterating over JSON objects.

It is called the KeyValuePipe - You can find more information in the documentation here: https://angular.io/api/common/KeyValuePipe

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

To access this new feature, make sure you are updated to the latest version of Angular 6 by running the following commands if you are already on Angular 6:

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

You can also read more about it in this article:

Answer №2

To resolve this issue, a TypeScript model object can be created to effectively map the incoming JSON file.

One approach is to utilize custom 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;
    }

}

Subsequently, an instance of the object model can be instantiated through the following:

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);
    })
}

Answer №3

When using ngFor, it is important to pay attention to the structure of your code:

currenciesJson = {};

In this example, the variable is initialized as an object.

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

Here, the same variable is being assigned a promise, which will not work as expected. It should be initialized as an array instead.

I recommend referring to the guide on HTTP to learn how to properly use HttpClient for data retrieval. Avoid using promises incorrectly and follow a pattern like the one below:

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);
}

Then, in the component:

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

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

By specifying types, the compiler can help identify any errors in your code.

Answer №4

When using *ngFor, it is important to remember that it can only iterate over Arrays, not Objects. Your response includes an object with a status (Object) and data (an array of objects). To loop over an array of objects, you can structure it like this:

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

In this case, you would only loop over currenciesJson.data.

To achieve this, you can use the following syntax:

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

Additionally, if you are using Angular 6.1 or above, you can loop over object keys using keyValue pipe. If you are using Angular 5, you can achieve this by using Object.keys(). However, it seems that in your case, the issue lies in looping over 'data' instead of 'keys'.

Answer №5

To start, avoid initializing like this:
replace currenciesJson = {}; with currenciesJson: object;
Then update 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>

Answer №6

After receiving valuable insights from the comments and answers on this post, I was able to reach my desired outcome.

The latest Key Value Pipe in Angular 6.1 was utilized for this implementation. https://angular.io/api/common/KeyValuePipe

Although it may seem a bit workaround, it served its purpose well. If I were to revisit this in the future, there is definitely room for improvement, but for now, it gets the job done.

Below are the contents of the three essential 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be causing the "buffer is not a function" error to occur?

As a beginner with observables, I'm currently working on creating an observable clickstream to avoid capturing the 2 click events that happen during a double-click. However, I keep encountering this error message:- Error: Unhandled Promise rejection ...

What is the appropriate event type to pass to the onKeyPressed function in a React application utilizing MaterialUI and written with Typescript?

I am currently working on a React application using Typescript and MaterialUI, where I have implemented a TextField component. My goal is to capture the value of the input HTML element when the user presses the enter key. To achieve this, I have created ...

Implement the TypeScript handleChange function for input fields and dropdown menus

Currently, I am in the process of developing a form using TypeScript and Material-UI components. My objective is to create a change handler function that can be utilized for both select and textfield changes. Below are my state and functions: const [re ...

Tips for resolving SyntaxError: Unable to utilize import when integrating Magic with NextJS in a Typescript configuration

Looking to integrate Magic into NextJS using TypeScript. Following a guide that uses JavaScript instead of TypeScript: https://github.com/magiclabs/example-nextjs Encountering an issue when trying to import Magic as shown below: import { Magic } from &qu ...

Managing optional props in Typescript React depending on the type of another prop

I am in the process of developing a fetcher component API. The concept is quite straightforward - you provide it with a fetcher (a function that returns a promise) and a params array (representing positional arguments) as props, and it will deliver the res ...

Optimal approach for designing interfaces

I have a situation where I have an object retrieved from the database, which includes assignee and author ID properties that refer to user objects. As I transform a number into a user object, I am unsure about the best practice for defining the type of the ...

Troubleshooting Problems with Angular Localization in EJ2 Syncfusion

I have been utilizing the Syncfusion Spreadsheet component to display data similar to an Excel spreadsheet. I successfully implemented all the necessary functionalities with Syncfusion documents, however, I am encountering a challenge. My current issue i ...

The attribute 'nameFormControl' is not a valid property on the 'ContactComponent' component

Greetings, StackOverflow community! I'm currently working on an Angular + Node app and I've run into a major problem related to forms and validators in Material Angular. Right now, I'm facing an issue where the HTML component is not recogn ...

Utilizing the combination of attribute binding and string interpolation

Looking to create an accordion similar to the one showcased on the Bootstrap website, but with the twist of dynamically loading data using Angular 2's *ngFor directive. I attempted setting the value of aria-controls dynamically like so: [attr.aria-co ...

Tips for selecting the correct type for an array of Unions with the help of Array.prototype.find

I have the following variations: type First = { kind: 'First', name: string } type Second = { kind: 'Second', title: string } type Combo = First | Second; I am attempting to locate the element of type First in a Combo[], as shown bel ...

What is the best way to modify an existing object in an Observable Array in Angular?

As I work on my Ionic 5 / Angular application, a challenge arises when attempting to update a Conversation object within the existing array of Conversation: private _conversations = new BehaviorSubject<Conversation[]>([ new Conversation( & ...

Differences in Array<T>.filter declaration and application

One issue that I'm currently facing involves a discrepancy between the usage of Array<T>.filter and the interface definition. In an Angular2 component, I have implemented the following filter: performFilter(filterBy: string): IProduct[] { ...

What is the best way to set up Storybook with Vue Cli 3?

I'm facing difficulties installing Storybook in a Vue Cli 3 project. Every time I try to npm run storybook, I encounter this error: Cannot find module '@storybook/vue/dist/server/config/defaults/webpack.config.js' I suspect that this i ...

Strategies for effectively conveying jQuery and Angular

I have integrated jQuery DataTable into my code. I am facing an issue with the dataTables_scrollBody class which requires me to add a custom scroll bar. This is necessary because on Linux, the touch screen functionality in Chrome is not working effectively ...

Tips for assigning a value to a Reactive Form control within Angular 6

I am looking to dynamically set the value of a text box when clicking on a button that is located outside of the form. How can I achieve this? <form [formGroup]='student' (ngSubmit)='click()'> <input type='text' form ...

Comparing two arrays in Angular through filtering

I have two arrays and I am trying to display only the data that matches with the first array. For example: The first array looks like this: ["1", "2" , "3"] The second array is as follows: [{"name": "xyz", "id": "1"},{"name":"abc", "id": "3"}, ,{"name ...

I am faced with a challenge involving an asynchronous function and the best approach to executing it synchronously

I devised the following plan: // Primary Function to Follow // Capture necessary local data // Transform into required editable format // Iterate through user's local images // Append image names to converted d ...

How can I enable autofocus on a matInput element when clicking in Angular 6?

Is there a way to set focus on an input element after a click event, similar to how it works on the Google Login page? I've tried using @ViewChild('id') and document.getElementId('id'), but it always returns null or undefined. How ...

Spring Boot receiving null values from Angular form submission

I am currently working on a form in Angular that is used to submit information such as author, context, and recently added images. However, I have run into an issue where I am able to successfully retrieve the author and context, but not the images (it alw ...

Event triggered before opening the cell editor in ag-Grid

I'm facing a scenario where I have a grid with rows. When a user double clicks on a cell, a cell editor opens up. There are events associated with the cellEditor - cellEditingStarted and cellEditingStopped, but I need an event that triggers just befor ...