14

Require help on Angular data-table with nested data.

I want to sort the data in table.

I am using data table from - https://www.npmjs.com/package/angular2-datatable

Data table works fine for single array type data. (used for many angular applications)

ISSUE: i have nested json (in reality, i have complex json, making here simple)

Thanks for looking into this.

Any suggestions or help is appreciated.

JSON

records = [
  [
    {
      "name": "Subject Name",
      "type": "text",
      "id": "subjectName",
      "value": "DavidJ",
      "firstName": "David",
      "lastName": "John"
    },
    {
      "name": "QC Name",
      "type": "hidden",
      "id": "qcName",
      "value": "JosephT",
      "firstName": "Joseph",
      "lastName": "Tom"
    }
  ],
  [
    {
      "name": "Subject Name",
      "type": "text",
      "id": "subjectName",
      "value": "TigerC",
      "firstName": "Tiger",
      "lastName": "Chan"
    },
    {
      "name": "QC Name",
      "type": "hidden",
      "id": "qcName",
      "value": "ThomasR",
      "firstName": "Thomas",
      "lastName": "Richard"
    }
  ]
]

HTML

<table class="table table-responsive table-hover" [mfData]="this.records | dataFilter : filterQuery" #mf="mfDataTable" [mfRowsOnPage]="rowsOnPage" [(mfSortBy)]="sortBy" [(mfSortOrder)]="sortOrder">
<thead>
   <tr>
      <th>#</th>
      <th>
         <mfDefaultSorter by="subjectName">subject Name</mfDefaultSorter>
      </th>
      <th>
         <mfDefaultSorter by="qcPerson">QC Person</mfDefaultSorter>
      </th>
   </tr>
</thead>
<tbody *ngIf="!isLoading">
   <tr class="border" *ngFor="let sample of mf.data; let i='index'">
      <td>{{i + 1}}</td>
      <ng-container *ngFor="let item of sample">
         <td *ngIf="item.id ==='subjectName'">
            {{item.firstName}} {{item.lastName}}
         </td>
         <td *ngIf="item.id ==='qcPerson'">
            {{item.firstName}} {{item.lastName}}
         </td>
      </ng-container>
   </tr>
</tbody>
</table>

TYpescript file

import { Component, OnInit } from '@angular/core';
import { OrderBy } from '../all_services/OrderByPipe';

@Component({
    selector: 'app-userdashboard',
    templateUrl: './userdashboard.component.html',
    styleUrls: ['../header-footer/css/external.style.css']
})

export class UserdashboardComponent implements OnInit {

    constructor() {}

    ngOnInit() {}


    /** Sorting functions */
    public data;
    public filterQuery = "";
    public rowsOnPage = 10;
    public sortBy = "subjectName";
    public sortOrder = "asc";

    public toInt(num: string) {
        return +num;
    }
}

Datafilterpipe.ts

import * as _ from "lodash";
import {Pipe, PipeTransform} from "@angular/core";

@Pipe({
    name: "dataFilter"
})
export class DataFilterPipe implements PipeTransform {

    transform(array: any[], query: string): any {
        if (query) {
            return _.filter(array, row=>row.name.indexOf(query) > -1);
        }
        return array;
    }
}
14
  • 1
    Your question is not clear - what you are you trying to do that you need help with? Commented Aug 14, 2018 at 7:02
  • 1
    @GabrielDoty , apologies if i am not clear. i want to sort the data in table. Commented Aug 14, 2018 at 7:40
  • 1
    why are you using array inside array, can't you simply put all objects inside one array, so that sorting of array of objects will be much easier Commented Aug 20, 2018 at 10:15
  • 1
    What about flattening the JSON before populating the table? angular-yfpnk4.stackblitz.io Commented Aug 20, 2018 at 10:38
  • 1
    @Prasanna Sasne, i am afraid i am unable to do that. i am using same json in many other components. i have compex nested json, here i am displaying very simple json Commented Aug 20, 2018 at 11:18

4 Answers 4

6
+50

I had face the same issue. I had used w3school table sort logic once the table is rendered in DOM.

It works really smooth with angular2-datatable as I am using the same datatable in my project. Its usage is very straight, stillif you face any issue please let me know.

Thanks in advance.

Below will be implementation Function in your TS file.

  columnSorter(n) {
    let table, rows, switching, i, x, y, shouldSwitch, dir, switchCount = 0;
    switching = true;
    this.clickSort = !this.clickSort
    dir = "asc";
    table = document.querySelector('.smallTable');
    while (switching) {
      switching = false;
      rows = table.rows;
      for (i = 0; i < (rows.length - 1); i++) {
        shouldSwitch = false;
        x = rows[i].getElementsByTagName("TD")[n];
        y = rows[i + 1].getElementsByTagName("TD")[n];
        if (dir == 'asc') {
          if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
            shouldSwitch = true;
            break;
          }
        } else if (dir == 'desc') {
          if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
            shouldSwitch = true;
            break;
          }
        }
      }
      if (shouldSwitch) {
        rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
        switching = true;
        switchCount++;
      } else {
        if (switchCount == 0 && dir == 'asc') {
          dir = 'desc';
          switching = true;
        }
      }
    }
  }

In you HTML table on th just add below, where 0 is column no.

(click)="columnSorter(0)
Sign up to request clarification or add additional context in comments.

11 Comments

It really doesn't fulfill my requirement. thanks for the idea. i really appreciate it.
If you dont mind can you tell me what is your requirement?
i want to sort the table headers by clicking up and down arrow arrow. I have 4 tables, i dont want users to have one more button to sort.
You dont require seperate button, call that function on column click.. Check my edited answer
@ManjunathSiddappa If my answer is solving your purpose then then please mark it as answer
|
5

Using this library you have to use these input variables to sort table once:

mfSortBy: any - sort by parameter
mfSortOrder: string - sort order parameter, "asc" or "desc"

Also you can add this tag to you th to allow user to sort it by click:

<mfDefaultSorter by="name">Name</mfDefaultSorter>

To create custom sort for the table you just need to sort yours json. In your case you should operate with what you assign to mf.data.

You can create custom derective where you will create sorter for a table and then sort data by click.

e.g.

import {
Directive, ElementRef, AfterViewChecked,
Input, Output, Renderer, EventEmitter
} from '@angular/core';

@Directive({
   selector: '[sorter], [defaultSorter]'
})
export class TableSorterDerective implements AfterViewChecked {
  @Input()
  sorter: {order:string, property:string};
  @Output()
  sorted = new EventEmitter();

  constructor(private el: ElementRef, private renderer: Renderer) {
  }

  ngAfterViewChecked() {
    let element: HTMLElement = this.el.nativeElement;
    if(this.sorter){
      this.addSorter(element);
    }
  }

  addSorter(column: HTMLElement){
    if(!column.classList.contains("custom_sorter")){
      column.addEventListener('click', () => this.sendSort(column), false)
      column.classList.add("custom_sorter");
    }
  }

  sendSort(element:HTMLElement){
    let columns: HTMLElement[] = 
Array.prototype.slice.call(element.parentElement.getElementsByTagName('th'), 0);
    columns.forEach(element => {
      if(!element.classList.contains(this.sorter.property)){
        let icon = element.getElementsByTagName('span')[0];
        if(icon) icon.remove();
      }
    });

    let icon:HTMLElement = element.getElementsByTagName('span')[0];
    if(!icon) icon = this.renderer.createElement(element, 'span');
    icon.classList.remove("glyphicon-triangle-bottom")
    icon.classList.remove("glyphicon-triangle-top")
    icon.classList.remove("glyphicon")

    if(this.sorter.order == "asc"){
      this.sorter = {order:"desc", property:this.sorter.property}
      icon.classList.add("glyphicon")
      icon.classList.add("glyphicon-triangle-top")
    }else if(this.sorter.order == "desc"){
      this.sorter = {order:"asc", property:this.sorter.property}
      icon.classList.add("glyphicon")
      icon.classList.add("glyphicon-triangle-bottom")
    }
    this.sorted.emit(this.sorter)
  }
}

and then you just need to sort data on emit:

    <th *ngFor="let col of columns" [sorter]="col.sorting ? {order:'desc', property:col.property} : undefined" (sorted)="transformationsService.sort(filteredData, $event)"</th>

To sort data just use sort function e.g. :

data.sort((a, b) => {
        return 0 - (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)
      }

See this question if you need help with sort function.

2 Comments

thank you let me check. i am not looking for custom sort, but i will try that as well.
you should know that this table allows you to sort only by first layer properties, so if you want to use not customized sorter you need to simplify input data to objects like {subjectName:string; qcPerson:string} and then you can use <mfDefaultSorter by="subjectName">Name</mfDefaultSorter>
5

To sort the data you could always use Lodash's very powerful _.sortBy:

import * as _ from 'lodash';

const nestedData = [
    { a: { b: 2} },
    { a: { b: 1} },
    { a: { b: 3} } 
];

// Outputs ​​​​​[ { a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 3 } } ]​​​​​
_.sortBy(nestedData, item =>  _.get(item, ['a', 'b']));

It even supports sorting by multiple fields:

import * as _ from 'lodash';

const nestedData = [
    { a: { b: 2 }, c: 'a' },
    { a: { b: 1 }, c: 'b' },
    { a: { b: 1 }, c: 'd'} 
];

// Output: 
​​​​​// [ { a: { b: 1 }, c: 'b' },​​​​​
​​​​​//   { a: { b: 1 }, c: 'd' },​​​​​
​​​​​//   { a: { b: 2 }, c: 'a' } ]​​​​​
_.sortBy(nestedData, [item =>  _.get(item, ['a', 'b']), item => _.get(item, 'c')]);

As far as adding this to your current table -- there are a lot of ways you can achieve that. It might be easier to pre-sort your data before you pass it the table. If the user clicks a column header -- simply run the data through the appropriate _.sortBy and dump it back into the table?

1 Comment

can you please give me fiddle or plunker with json, if possible please
5

Instead of using dataTable you can implement custom pipe, which will sort the data, without changing original object. Some how you want to sort the data when you clicked on columns. Here is simple code, hope this works for you.

import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
  name: "orderby",
  pure: false
})
export class OrderByPipe implements PipeTransform {
  transform(array: Array<any>, args?: any) {
    let newDataArray:any = [];      
        if (array.length > 0 && args.value != undefined) {
        for (let item of array) {
            for (let subItem of item) {
                newDataArray.push(subItem);
            }
        }
        newDataArray.sort((a: any, b: any) => {
                    if ((a.firstName).toLowerCase() < (b.firstName).toLowerCase()) {
                        return -1 * args.direction;
                    } else if ((a.firstName).toLowerCase() > (b.firstName).toLowerCase()) {
                        return 1 * args.direction;
                    } else {
                        return 0;
                    }
                });
        array = Array.from(newDataArray);  
        }
       return array;
     }
    }           

To call this pipe from your component use code as below, define these variables

isDesc: boolean = false;
direction: number = 1;
filterData = [];

and

  orderByMe(toBeSorted: string){
     this.isDesc = !this.isDesc;
     this.direction = this.isDesc ? 1 : -1;
     this.filterData = Array.from(new OrderByPipe().transform( this.records, { 
     value: toBeSorted, direction: this.direction } ));
   } 

  ngOnInit(){
    this.orderByMe('subjectName');
  }

In Html template you can use

<table>
<thead>
<tr>
  <th>#</th>
  <th (click)="orderByMe('subjectName')">subjectName</th>
  <th (click)="orderByMe('qcName')">qcPerson</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let record of filterData; let currentIndex = index;">
  <td>{{ currentIndex + 1}}</td>
  <td>{{ record.firstName }}</td>
  <td>{{ record.lastName }}</td>
</tr>
</tbody>
</table>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.