Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 data-table sorting from nested json

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;
    }
}
like image 853
Manjunath Siddappa Avatar asked Aug 13 '18 12:08

Manjunath Siddappa


3 Answers

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)
like image 84
Krishna Avatar answered Oct 24 '22 09:10

Krishna


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.

like image 41
Vlad Kostyukevich Avatar answered Oct 24 '22 07:10

Vlad Kostyukevich


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?

like image 5
Pearman Avatar answered Oct 24 '22 08:10

Pearman