Display nested object in column in primeng
Asked Answered
P

6

12

I am following the templating option given in primeng docs to create a link with column data alongside in a primeng datatable column, but I am not able to show nested object using {{data[col.field]}}.

<p-column [field]="col.field" [header]="col.header" [sortable]="col.sortable" [filter]="col.filter" [editable]="col.editable" [filterPlaceholder]="col.filterPlaceholder" styleClass="{{col.class}}">
                <ng-template let-col let-data="rowData" let-ri="rowIndex" pTemplate="body">
                    <span *ngIf="!col.field.includes('.')" >{{data[col.field]}}</span>
                    <!-- <span *ngIf="col.field.includes('.')">{{data[col.field.split('.')[0]][col.field.split('.')[1]]}}</span> this does not work because field is like x.y-->
                    <!-- I have some other buttons here as well --> 
                </ng-template>
        </p-column>

How can I acheive this?

Sharing entire code -->

<p-dataTable [globalFilter]="gb" [filterDelay]=1000 [value]="tableData" [alwaysShowPaginator]="true" [rowStyleClass]="setStyle" [rows]="rows" [paginator]="paginate" [alwaysShowPaginator]="false" [resizableColumns]="true" tableStyleClass="table-wrap {{rowClass}}"
    [rowsPerPageOptions]="[5,10,20]" expandableRows="{{setExpander}}" [editable]="setEditable" (onRowClick)="handleRowSelect($event)" [lazy]="pagination" [totalRecords]="totalRecords" (onLazyLoad)="loadLazy($event)" [ngClass]="{'paginator-table': pagination}">
    <div *ngFor="let col of tableOptions.columns, let index=index, let odd=odd, let even=even">
        <p-column *ngIf="col.field" [field]="col.field" [header]="col.header" [sortable]="col.sortable" [filter]="col.filter" [editable]="col.editable" [filterPlaceholder]="col.filterPlaceholder" styleClass="{{col.class}}">
            <ng-template let-col let-data="rowData" let-ri="rowIndex" pTemplate="body">
                    <span *ngIf="!col.field.includes('.')" >{{data[col.field]}}</span>
                    <!-- <span *ngIf="col.field.includes('.')">{{data[col.field.split('.')[0]][col.field.split('.')[1]]}}</span> this does not work because field is like x.y-->
                <a *ngIf="col.field === 'ticket'" target="_blank" href={{link}}{{data[col.field]}}><i class="fa fa-external-link" aria-hidden="true"></i></a>
            </ng-template>
        </p-column>
    </div>
</p-dataTable>
Phenacaine answered 8/1, 2018 at 6:54 Comment(4)
Can you share the entire p-datatable html code and the typescript which goes with it please ? Or even better, create a Plunker ? Also, you should remove the square brackets of the field property.Urbannai
If I make it a flat object everything works fine but how can I make it work for nested object.Phenacaine
Can you share the relevant typescript code please ?Urbannai
I am following same code like example here but the data I get from backend is a nested obj for some fields. For nested obj, we can write car.color etc right?Phenacaine
S
23

PrimeNG DataTable is deprecated, use Table (AKA TurboTable) instead. https://www.primefaces.org/primeng-5-2-0-rc1-released-turbotable/

Anyways, you could access nested object inside Data-Table as follows:

<p-table [columns]="cols" [value]="data" ... >
  ...
  // Definition of table body
  <ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr [pSelectableRow]="rowData">
      <td *ngFor="let col of columns">
         <div *ngIf="col.subfield;then nested_object_content else normal_content"></div>
         <ng-template #nested_object_content>
           {{rowData[col.field][col.subfield]}}
         </ng-template>
         <ng-template #normal_content>
           {{rowData[col.field]}}
         </ng-template>
      </td>
    </tr>
  </ngTemplate>
  ...
</p-table>

and in your component:

public data = [
{
  field1: {
    subfield1: 'test'
  },
  field2: 'test',
  field3: 'test',
  field4: {
    subfield4: 'test'
  }
}]

this.cols = [
  { field: 'field1', subfield: 'subfield1'},
  { field: 'field2'},
  { field: 'field3'},
  { field: 'field4', subfield: 'subfield4'},
];

I hope this helps you. :)

Sponson answered 25/1, 2018 at 10:48 Comment(2)
and what about sorting of that subfield?Polysynthetic
@Polysynthetic did you find an answer?Vary
M
9

As a follow-up to what Bandeeta said about TurboTable: this solution can handle multiple nested properties instead of just one subfield:

<tr [pSelectableRow]="row">
  <td *ngFor="let col of columns">
    <span>{{ getCellData(row, col) }}</span>
  </td>
</tr>

And in your component:

getCellData(row: any, col: any): any {
   const nestedProperties: string[] = col.field.split('.');
   let value: any = row;
   for (const prop of nestedProperties) {
     value = value[prop];
   }

   return value;
 }
Melancholia answered 26/4, 2018 at 13:9 Comment(2)
Nice... Just to avoid null reference access, you can replace with >> value = value ? value[prop] : ''; << inside for loop.Charming
yes but this won't work for editable binding (ie. ngModel)Hamer
D
4

You can do this using Angular Custom Pipe. Sample here.

app.component.html

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                    {{rowData|field:col}}
            </td>
        </tr>
    </ng-template>
</p-table>

app.component.ts

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

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  cars = [
    {
      year: 2019,
      brand: {
        name: "Toyota"
      },
      color: "White",
      passengers: [
        {
          name: "John"
        }
      ]
    },
    {
      year: 2018,
      brand: {
        name: "Toyota"
      },
      color: "White",
      passengers: [
        {
          name: "Suzanne"
        }
      ]
    },
    {
      year: 2017,
      brand: {
        name: "Toyota"
      },
      color: "White",
      passengers: [
        {
          name: "Gökhan"
        }
      ]
    }
  ];
  cols: any[];
  constructor() {}

  ngOnInit() {
    this.cols = [
      { field: "year", header: "Year" },
      { field: "brand.name", header: "Brand" },
      { field: "color", header: "Color" },
      { field: "passengers.0.name", header: "Passengers" }
    ];
  }
}

Running example is here.

https://stackblitz.com/edit/angular6-primeng-qhxptl

Dustidustie answered 11/2, 2020 at 5:37 Comment(1)
Worked like a charm, the stackblitz demo was exactly what I was looking for. Thanks @Dustidustie for a simple solutionPassional
P
1

There is a great article on how to do this in a type safe way here but if safety isn't an issue, you can use Lodash' get function which saves having to deviate from the PrimeNG basic table use with crazy subfield stuff.

// in .ts file
import {get} from 'lodash';

// give it a different name if you like
_ = get;

// columns
this.columns = [
    {field: 'user.address.postcode', header: 'Post Code'}
]

Then in the html

<p-table [columns]="columns" [value]="users$ | async" styleClass="p- 
datatable-sm">
<ng-template pTemplate="header" let-columns>
<tr>
  <th *ngFor="let col of columns">
    {{col.header}}
  </th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns">
<tr>
  <td *ngFor="let col of columns">
    {{_(rowData, col.field)}}
  </td>
</tr>
</ng-template>
</p-table>
Pskov answered 20/8, 2020 at 8:6 Comment(0)
M
0

This might be a little bit late, but I ended up with a bit different solution. I have my own table component based on p-table and I bind the columns, rows, etc.

I created a component specifically for this, then I bind the current row and column

<ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr [pSelectableRow]="rowData">
        <td *ngFor="let col of columns">
          <app-table-column [column]="col" [row]="rowData"></app-table-column>
        </td>
    </tr>
</ng-template>

This is my table-column component, I'm sharing a very basic stuff but you can improve it as you wish/need.

I'm using lodash to get the row's value based on the field (column), it works for dotted (nested properties) or for flat properties.

    import { Component, Input, OnInit } from '@angular/core';
import * as moment from 'moment';
import * as _ from 'lodash';

@Component({
  template: `
    <span>
      {{ value }}
    </span>
  `,
  selector: 'app-table-column',
})
export class TableColumnComponent implements OnInit{

  @Input() column;
  @Input() row;

  value: any;

  constructor() {}

  ngOnInit(): void {
    this.parseValue(_.get(this.row, this.column.field));
  }

  parseValue(value) {
    switch (this.column.type) {
      case 'date':
        this.value = moment(value);
        break;

      default:
        this.value = value;
      break;
    }
  }

}
Microbicide answered 28/6, 2019 at 21:48 Comment(0)
A
0

this.cols = [
      { field: 'value1', header: 'Value 1' },
      { field: 'value2', header: 'Value 2', element: true },
    ];
<td class="" *ngFor="let col of columns">
  <span class="ui-column-title">{{ col.header }} : </span>
  {{col.element ? rowData[col.field].label : rowData[col.field]}}
</td>
Alrzc answered 11/10, 2020 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.