'sorting in Angular4 based on header clicked
I am trying to implement sorting on Angular4 , when a header is clicked . I want the whole result set to be sorted on the field clicked. I was able to use something like this in Angular1
ng-click="sortType = 'empId'; sortReverse = !sortReverse;"
And my ng-repeat was something like this
ng-repeat="employees in searchresponse|orderBy:sortType:sortReverse"
How can i implement something like this or something different which allows me to sort fields in ascending/descending order when clicked . This is what i have started with Angular4 and stuck currently
<table class="table table-bordered table-fixed table-striped text-center">
<tr style=" background-color: #bccbd4 ">
<td class="tabH1" align="center">
<a>
<u>EmpId</u>
</a>
</td>
<td class="tabH1" align="center">
<a>
<u>Name</u>
</a>
</td>
<td class="tabH1" align="center">
<a>
<u>Mobile-Num</u>
</a>
</td>
</tr>
<tr *ngFor="let emp of empList">
<td align="center">{{emp.id}}</td>
<td align="center">{{emp.name}}</td>
<td align="center">{{emp.mobile}}</td>
</tr>
</table>
My sample JSON
this.empList = [
{
id: '1',
name: 'test',
mobile: '1234567890'
},
{
id: '2',
name: 'kumar',
mobile: '9786543210'
}
];
When id is clicked, i want the whole JSON to be sorted based on the empId .should i create my own Pipe or should i create a function to sort the resultset . Any example on how to proceed is greatly appreciated .
Solution 1:[1]
You can use a directive to achieve the same. The advantage of using directive is that you can use it anywhere in you other components as well.
Your html would be something like this
<table>
<thead>
<th sortColumn [sortKey]="'id'" [data]="data">ID</th>
<th sortColumn [sortKey]="'name'" [data]="data">Name</th>
<th sortColumn [sortKey]="'mobile'" [data]="data">Mobile</th>
</thead>
<tbody>
<tr *ngFor="let item of data">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.mobile}}</td>
</tr>
</tbody>
</table>
where sort key is the column on which you want to sort the data, sortColumn is the directive selector and data is you entire array of data.
And your directive would be
@Directive({selector: '[sortColumn]'})
export class SortDirective implements OnInit {
@Input() data: any[];
@Input('sortKey') key: any;
private toggleSort: boolean = false;
constructor (private el: ElementRef, private renderer: Renderer) {
}
ngOnInit () {
this.renderer.listen(this.el.nativeElement, 'click', (event) => {
let parentNode = this.el.nativeElement.parentNode;
let children = parentNode.children;
if (this.data && this.key) {
let sortedData: any = this.sortArray();
}
this.toggleSort = !this.toggleSort;
})
}
sortArray (): Array<any> {
let tempArray: Array<any> = this.data;
tempArray.sort((a, b) => {
let aKey = a[this.key];
let str1: string = a[this.key].toLowerCase();
let str2: string = b[this.key].toLowerCase();
if (this.toggleSort) {
if (str1 < str2) {
return -1;
}
if (str1 > str2) {
return 1;
}
}
else {
if (str1 > str2) {
return -1;
}
if (str1 < str2) {
return 1;
}
}
return 0;
});
return tempArray;
}
}
You can check the working example here. https://angular-sey5es.stackblitz.io
Solution 2:[2]
You can use Array.prototype.sort
empList.sort(function(a, b) {return b.id -a.id >0})
Solution 3:[3]
I would add a (click)="sortByEmpId()"
listener on the td
for empId to call a function that sorts your empList. In the function you can have code that sorts your empList however you want.
If the list is sorted any particular order when you first retrieve it (assuming you retrieve it) you can simply call Array.prototype.reverse() on it for subsequent clicks. If it isn't use Array.prototype.sort() to sort it and then reverse it for subsequent clicks.
Sorting/Ordering by pipes is not recommended for reasons explained here: https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
Solution 4:[4]
You can use an observable and, when the observable change sort the lista
lista: IData[]; //<--your List
sortType: string = "id"; //<--variables
sortReverse: boolean = false;
private listaChangeSource = new Subject<boolean>();
listaChange = this.listaChangeSource.asObservable();
ngOnInit() {
this.listaChange.subscribe(() => {
if (!this.lista)
return null;
..here you order the list according this.sortType and this.sortReverse..
}
}
/*
<th class="col-min">
<button class="btn btn-link" (click)="sortList('ID')">
#
<span [ngClass]="{'fa fa-caret-down':sortType=='ID' && !sortReverse}"></span>
<span [ngClass]="{'fa fa-caret-up':sortType=='ID' && sortReverse}"></span>
</button>
</th>
*/
//You call this function in your html
sortList(sortType: string) {
if (this.sortType == sortType)
this.sortReverse = !this.sortReverse;
this.sortType = sortType;
this.listaChangeSource.next(true); //<--call the listaChange.subscribe
}
more simple I suppose call the sortFunction without call a listaChangeSource.next
function sortLista()
{}
sortList(sortType: string) {
if (this.sortType == sortType)
this.sortReverse = !this.sortReverse;
this.sortType = sortType;
this.sortLista();
}
Solution 5:[5]
export class AppComponent implements OnInit {
sortByAsc: boolean = true;
constructor() { }
ngOnInit() { }
sortTable(parm) {
if(this.sortByAsc == true) {
this.sortByAsc = false;
this.filteredData.sort((a, b) => {
return a[parm].localeCompare(b[parm]);
});
} else {
this.sortByAsc = true;
this.filteredData.sort((a, b) => {
return b[parm].localeCompare(a[parm]);
});
}
}
}
html
<th (click)="sortTable(id)">id</th>
Solution 6:[6]
Angular material have a solution for this using matSort, it also comes with an arrow for asc and desc sort, see this link, although you still need to write the sort logic yourself (they show you how to do it), here is the example they provide:
import {Component} from '@angular/core';
import {Sort} from '@angular/material/sort';
export interface Dessert {
calories: number;
carbs: number;
fat: number;
name: string;
protein: number;
}
/**
* @title Sorting overview
*/
@Component({
selector: 'sort-overview-example',
template: `
<table matSort (matSortChange)="sortData($event)">
<tr>
<th mat-sort-header="name">Dessert (100g)</th>
<th mat-sort-header="calories">Calories</th>
<th mat-sort-header="fat">Fat (g)</th>
<th mat-sort-header="carbs">Carbs (g)</th>
<th mat-sort-header="protein">Protein (g)</th>
</tr>
<tr *ngFor="let dessert of sortedData">
<td>{{dessert.name}}</td>
<td>{{dessert.calories}}</td>
<td>{{dessert.fat}}</td>
<td>{{dessert.carbs}}</td>
<td>{{dessert.protein}}</td>
</tr>
</table>`,
})
export class SortOverviewExample {
desserts: Dessert[] = [
{name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4},
{name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4},
{name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6},
{name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4},
{name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4},
];
sortedData: Dessert[];
constructor() {
this.sortedData = this.desserts.slice();
}
sortData(sort: Sort) {
const data = this.desserts.slice();
if (!sort.active || sort.direction === '') {
this.sortedData = data;
return;
}
this.sortedData = data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch (sort.active) {
case 'name':
return compare(a.name, b.name, isAsc);
case 'calories':
return compare(a.calories, b.calories, isAsc);
case 'fat':
return compare(a.fat, b.fat, isAsc);
case 'carbs':
return compare(a.carbs, b.carbs, isAsc);
case 'protein':
return compare(a.protein, b.protein, isAsc);
default:
return 0;
}
});
}
}
function compare(a: number | string, b: number | string, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gautam |
Solution 2 | Maxim |
Solution 3 | |
Solution 4 | |
Solution 5 | Gopala raja naika |
Solution 6 |