Daniel Grindstaff
Daniel Grindstaff

Reputation: 105

*ngFor looping failing to display json data in Angular 9

I am reading a local json file in my Angular 9 app and trying to display the results to the app.component.html. I have spent quite a bit of time here researching and trying different techniques as *ngFor looping through the data returned by the httpClient.get call, stored in the this.initialData variable, as well as using techniques to convert the data set into an an array of the object arrays, stored in the this.dataValues variable. I have posted screenshots after each looping attempt, replacing the variable. I would appreciate any feedback on how to implement this as of now the data does not render to the page but rather throws errors to the console which I will post. I am able to =use console.log() to see that the variables are populated with the JSON data.

Here is the json file:

{
  "data": [
    {
      "id": 18,
      "internal_name": "scone_birthday",
      "type": "coupon",
      "description": "Free Scone awarded for a members birthday",
      "heading": "Toast to your birthday!",
      "subheading": "Our gift to you",
      "body": "This is the body for the <span style='font-family: \"Times New Roman\", Times, serif;'><strong><em><span style=\"color: rgb(0, 198, 255);\">birthday offer.</span></em></strong></span><span style=\"font-family: Georgia,serif;\"></span>",
      "subject": "",
      "details": "This is the details for the birthday <strong>offer</strong>.",
      "action": "",
      "image1_bg": "",
      "image_url": "images/birthday_candle.png",
      "respondable": true,
      "metric_amount": "150.0",
      "metric_name": "spend",
      "usage_end_date": "2020-11-05T23:59:59.000+11:00"
    },
    {
      "id": 4,
      "internal_name": "voucher",
      "type": "coupon",
      "description": null,
      "rank": "1.0",
      "start_date": null,
      "end_date": null,
      "heading": "HighFIVE!",
      "subheading": "You&#39;ve got $5 of dough*",
      "body": "Redeem for a $5 reward. <a href=\"https://en.wikipedia.org/wiki/Lorem_ipsum\" rel=\"noopener noreferrer\" target=\"_blank\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
      "subject": "Subject",
      "details": "",
      "action": "",
      "image1_bg": "",
      "image_url": "images/five_dollar.png",
      "respondable": true,
      "metric_amount": "200.0",
      "metric_name": "point",
      "usage_end_date": "2020-11-08T23:59:59.000+11:00"
    },
    {
      "id": 19,
      "internal_name": "loaf_welcome",
      "type": "coupon",
      "description": "Onboarding offer for member - free loaf ",
      "start_date": null,
      "end_date": null,
      "heading": "Get a slice of delight",
      "subheading": "Treat Yourself",
      "body": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry&#39;s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
      "subject": "",
      "details": "",
      "action": "",
      "image1_bg": "",
      "image_url": "images/gift.png",
      "respondable": true,
      "metric_amount": "100.0",
      "metric_name": "spend",
      "usage_end_date": "2020-12-30T23:59:59.000+11:00"
    }
  ]
}

the my app.component.ts

import {Component, OnInit} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {RewardsService} from './rewards.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  title = 'My Rewards';
  dataValues: Array<object> = [];
  initialData;

  constructor(private rewardsService: RewardsService, private http: HttpClient) {}

  ngOnInit(): void {
    this.http.get('assets/rewards.json').subscribe(data => {
      console.log(data);
      this.initialData = data;
      for (const element in this.initialData) {
        this.dataValues.push({
          id: element,
          name: this.initialData[element]
        });
      }
      console.log(this.dataValues);
    });
  }
}

the app.component.html with the initialData variable:

<app-header></app-header>
<table class="table table-striped">
  <thead>
  <tr>
    <td>Data Values 1</td>
    <td>Data Values 2</td>
    <td>Data Values 3</td>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let column of initialData[0]; let indx = index">
    <td>
      {{initialData[0][indx]}}
    </td>
    <td>
      {{initialData[1][indx]}}
    </td>
    <td>
      {{initialData[2][indx]}}
    </td>
  </tr>
  </tbody>
</table>

It's corresponding screenshot: intialData variable screenshot

the app.component.html with the dataValues variable:

<app-header></app-header>
<table class="table table-striped">
  <thead>
  <tr>
    <td>Data Values 1</td>
    <td>Data Values 2</td>
    <td>Data Values 3</td>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let column of dataValues[0]; let indx = index">
    <td>
      {{dataValues[0][indx]}}
    </td>
    <td>
      {{dataValues[1][indx]}}
    </td>
    <td>
      {{dataValues[2][indx]}}
    </td>
  </tr>
  </tbody>
</table>

the accompanying screenshot: enter image description here

Any help is greatly appreciated!

Upvotes: 2

Views: 1197

Answers (2)

Tomasz Golinski
Tomasz Golinski

Reputation: 728

the problem is that you are trying to iterate through an object instead of an Array initialData[0]

if you do this

<tr *ngFor="let column of initialData.data; let indx = index">

or even better do this

//  app ts
    this.http.get('assets/rewards.json').subscribe({ data } => {
    ...
    }

// and then in html you can keep it like this

<tr *ngFor="let column of initialData; let indx = index">

when you get an error saying that ngFor can only use Iterable objects, it pretty much means you are not iterating over a list...

it should work fine

But tbh, I'm not sure what you are trying to accomplish here, if you are trying to go over each key as a column then you should first extract keys

// ts file
const columns = Object.keys(this.initialData)

// and in html
<tr *ngFor="let c of columns">
    <td> {{ initialData[0][c] }} </td>
    <td> {{ initialData[1][c] }} </td>
    <td> {{ initialData[2][c] }} </td>
</tr>

or a better solution and a safer one (not hardcoded number of items)

// ts file
const columns = Object.keys(this.initialData)

// and in html
<tr *ngFor="let c of columns">
    <td *ngFor="let data of initialData"> {{ data[c] }} </td>
</tr>

Upvotes: 3

Barremian
Barremian

Reputation: 31115

Not exactly sure what you're attempting to do but it seems the array is actually inside the nested property data. You could use Array#map to extract only the specific property and use async pipe in the template to avoid the subscription.

Try the following

Controller

import { Component, OnInit } from "@angular/core";

import { Observable } from "rxjs";
import { map } from "rxjs/operators";

@Component({ ... })
export class HomeComponent implements OnInit {
  dataValues$: Observable<any>;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataValues$ = this.http.get('assets/rewards.json')
      .getRewards()
      .pipe(
        map((data: any) => (data.data as Array<any>).map((d: any) => d.body))
      );
  }
}

Template

<ng-container *ngIf="(dataValues$ | async) as dataValues">
    <table class="table table-striped">
        <thead>
            <tr>
                <td>Data Values 1</td>
                <td>Data Values 2</td>
                <td>Data Values 3</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td *ngFor="let column of dataValues">
                    <span [innerHTML]="column | safeHtml"></span>
                </td>
            </tr>
        </tbody>
    </table>
</ng-container>

Safe pipe

import { Pipe, PipeTransform } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";

@Pipe({ name: "safeHtml", pure: true })
export class SafePipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(value: string): any {
    return this.sanitizer.bypassSecurityTrustHtml(value);
  }
}

Working example: Stackblitz

Update: iterate the array in controller

To use the response in the controller, you could skip the async pipe and subscribe in the controller.

Try the following

export class HomeComponent implements OnInit {
  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.dataService.getRewards().subscribe({
      next: (data: any) => {
        data.data.forEach((item: any) => {
          console.log(item);
          console.log("id:", item.id);
          console.log("internal_name:", item.internal_name);
          // do something else
        });
      }
    });
  }
}

Working example: Stackblitz

Upvotes: 1

Related Questions