0

In Angular, I want to convert a JSON array to an HTML table.

I have seen an old answer for AngularJS:

<table>
    <thead>
      <tr>
        <th ng-repeat="(key, value) in records[0]">{{key}}</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="(key, value) in records">
        <td ng-repeat="(key, value) in value">
          {{value}}
        </td>
      </tr>
    </tbody>
</table>

JSON looks like this:

[{
    "Name": "Alfreds Futterkiste",
    "City": "Berlin",
    "Country": "Germany"
}, {
    "Name": "Berglunds snabbköp",
    "City": "Luleå",
    "Country": "Sweden"
}, {
    "Name": "Centro comercial Moctezuma",
    "City": "México D.F.",
    "Country": "Mexico"
}]

I've tried to translate it to the Angular syntax. Here is what I got so far:

<table>
    <thead>
      <tr>
        <th *ngFor="let item of records[0]  | keyvalue">{{item.key}}</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let item of records">
        <td *ngFor="let item1 of item | keyvalue">
          {{item1.value}}
        </td>
      </tr>
    </tbody>
</table>

Right now it's failing to compile because records[0] is undefined... how can I translate this expression to the newer syntax (or create something equivalent)?

UPDATE 1:

I have a partial solution. However with this partial solution the rendered table is not completely identical to the older AngularJS rendition (because it creates multiple unnecessary header rows, which only one of them is populated, as opposed to only one header row in the older rendition).

<table style="border-collapse: collapse;">
    <thead *ngFor="let item of records; let last=last">
      <tr *ngIf="last">
        <th *ngFor="let item1 of item | keyvalue">
          {{item1.key}}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let item of records">
        <td *ngFor="let item1 of item | keyvalue">
          {{item1.value}}
        </td>
      </tr>
    </tbody>
</table>

Do you have a better way to do it, possibly similar to the older AngularJS version?

UPDATE 2:

In Angular, I access the JSON data through a request from Angular that is then redirected to a back end service. That service may read a file, or get the data from a database. When the back end service has the data ready, it returns the data to the Angular request. The code on the Angular end looks like this:

  HTML:

  <div>
    <h3>Test Post Request</h3>
    <button (click)="postData()">Click Me</button>
    <div>Response: {{records}}</div>
  </div>

  TypeScript:

  private dataPostTestUrl = '/api/postTest';
  records: string | undefined;

  public postData(): void {
    this.appService.sendData().subscribe((data: any) => {
      this.records = data.content;
    });
  }

  public sendData(): Observable<any> {
    return this.http.post(this.dataPostTestUrl, {});
  }
rapt
  • 11,810
  • 35
  • 103
  • 145
  • Hard to tell from the provided info but you might be rendering before the API call returns. Try adding `*ngIf=“records”` to the table tag. – The Head Rush May 13 '22 at 11:20
  • @TheHeadRush No. – rapt May 13 '22 at 11:29
  • More info needed. Would be good to see component code around how you fetch the data. – The Head Rush May 13 '22 at 11:40
  • @TheHeadRush The syntax you suggest is not valid. Anyway, `records` does have all the JSON data at this point. It's just that `records[0]` might not be valid syntax there for some reason. – rapt May 13 '22 at 11:42
  • How do you import the JSON into the component? – Viktor May 13 '22 at 11:53
  • @Viktor From a file. I can render the whole data if I print all the keys (for every row) and then all the values (for every row). However I only want the keys once (e.g for row #0). So it's not a synchronization problem. `Compiled with problems: ERROR ... error TS2532: Object is possibly 'undefined'... let item of records^[0]` – rapt May 13 '22 at 12:40

2 Answers2

0

I think maybe you need to define records in the component.

records = [{
    "Name": "Alfreds Futterkiste",
    "City": "Berlin",
    "Country": "Germany"
}, {
    "Name": "Berglunds snabbköp",
    "City": "Luleå",
    "Country": "Sweden"
}, {
    "Name": "Centro comercial Moctezuma",
    "City": "México D.F.",
    "Country": "Mexico"
}];
Metalgear
  • 3,391
  • 1
  • 7
  • 16
0

I am fairly certain this is what you need: https://stackblitz.com/edit/angular-ivy-n7irpw?file=src/app/hello.component.ts

Take a look at how I import the file in the app.component.ts As well as how I loop in the HTML.

Let me know if it helps!

Viktor
  • 66
  • 7
  • Can you explain my update? – rapt May 13 '22 at 14:41
  • I'd need a replication of the issue to have a better look. As of now, my best guess is that your `record[0]` does not return the proper data. Please make a minimal reproduction scenario and I will take a look. – Viktor May 13 '22 at 14:53
  • I will try to do it. Also, I noticed that in your code the columns (fields) were printed in an alphabetic order (from left to right). Is it possible to print them in the order they appear in the JSON file (i.e. name, city, country)? – rapt May 13 '22 at 15:19
  • It is possible you just need to tweak the key-value pipe as it orders them alphabetically. See the Angular docs: https://angular.io/api/common/KeyValuePipe – Viktor May 13 '22 at 16:34
  • I was not able to make my demo project run on stackblitz... it's stuck... (I guess after 5 years they are still a work in progress). But I provided more details in my "Update 2" above. Please take a look. It seems like when I invoke the `postData()` function (by a button), `records` already has all the data when the html rendition starts. Is it not what `.subscribe((data) => ...)` supposed to do? Also pay attention that it is a compilation error. I see nothing because the project doesn't compile. It's not (yet) a runtime problem of reading a `Future` that has not completed yet its course. – rapt May 13 '22 at 21:20
  • Well, there are multiple weird parts of the second update. First `records: string | undefined` does not make sense since "the records field is an array this maybe causes the compile issue. Second, the main point of the subscribe function is to get the response of the call if it is needed but if you want the response div not to show up add an `*ngIf='response'` to only display the div when there is data in the records field. – Viktor May 15 '22 at 19:23