Angular using javascript to make sort Pipe - arrays

I have a question when I build sort pipe in Angular2
here is my pipe code:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'sort'
})
export class SortPipe implements PipeTransform {
transform(value: any, propName: string): any {
return value.sort((a,b)=> {
return a[propName]-b[propName];
// if(a[propName]>b[propName]) {
// return 1;
// } else {
// return -1;
// }
});
}
}
When I use the code in comments, the pipe works, but when I use return a [propName]-b[propName]; it is not working;

For sort to work, you must return an integer (see reference). Are you sure, that subtracting your properties will always return these values?

Depends on what you are sorting. If the value is a number it should work. If it's a string then a NaN is returned. Second solution is better.

This code selects the property of the list of items objects passed in by the pipe and takes into consideration null values.
Below you can see what the pipe will look like with the *ngFor:
<tr *ngFor="let item of Clients | sort: sortType: sortDirection">
Below you can see when the column header is clicked, the arrows change direction of the arrow and make the sort output ascending or descending within the sort pipe.
<th>
<div class="d-flex flex-row hoverOver">
<span class="text-nowrap">Quantity</span>
<i class="fa hoverTrans align-self-center mx-1" [ngClass]="{'fa-arrows-v': column != 'created',
'fas fa-long-arrow-up': !sortDirection,
'fas fa-long-arrow-down': sortDirection }" (click)="sortType = 'qty'; sortDirection = !sortDirection">
</i>
Below is the Sort Pipe:
transform(items: any, sortType: any, sortDirection: any): any {
const direction = sortDirection ? -1 : 1;
items.sort((a, b) => {
if (a[sortType] < b[sortType] || (a[sortType] === null && b[sortType] !== null) || (a[sortType] === "" && b[sortType] !== null)) {
return -1 * direction;
} else if (a[sortType] > b[sortType] || (a[sortType] !== null && b[sortType] === null) || (a[sortType] !== null && b[sortType] === "")) {
return 1 * direction;
} else {
return 0;
}
});
return items;
}

Related

Filter array of objects on multiple keys

I have a react table that I am trying to filter on multiple columns using a filter function. If i filter on one column its fine but if i add another column it filters only by that and not both.
Example would be the name "Scott". I want to filter the first_name column by it and also the biz_name column by it. But when I check the box to change state for that column, it only filters on one. Here is the checkbox in which I have checked state to make sure it is working correctly.
<Checkbox
label="Business Name"
onCheck={event => {
if (event.target.checked) {
this.setState({
filterBusinessName: true
});
} else {
this.setState({
filterBusinessName: false
});
}
}}
/>
<Checkbox
label="First Name"
onCheck={event => {
if (event.target.checked) {
this.setState({
filterFirstName: true
});
} else {
this.setState({
filterFirstName: false
});
}
}}
/>
And then here is the filter function above the table:
let items = this.state.contacts
if (this.state.term && items.length > 0) {
let searchTerm = this.state.term.toLowerCase()
items = items.filter(row => {
if(this.state.filterBusinessName && row.biz_name){
return row.biz_name.toLowerCase().includes(searchTerm)
}
if(this.state.filterFirstName && row.first_name){
return row.first_name.toLowerCase().includes(searchTerm)
}
if(this.state.filterFirstName && row.first_name && this.state.filterBusinessName && row.biz_name){
return row.first_name.toLowerCase() == searchTerm || row.biz_name.toLowerCase() == searchTerm
}
})
}
I think you want something like this
let items = this.state.contacts;
if (this.state.term && items.length > 0) {
let searchTerm = this.state.term.toLowerCase();
items = items.filter(row => {
if (
this.state.filterBusinessName &&
row.biz_name &&
row.biz_name.toLowerCase().includes(searchTerm)
) {
return true;
}
if (
this.state.filterFirstName &&
row.first_name &&
row.first_name.toLowerCase().includes(searchTerm)
) {
return true;
}
return (
this.state.filterFirstName &&
row.first_name &&
this.state.filterBusinessName &&
row.biz_name &&
(row.first_name.toLowerCase() == searchTerm ||
row.biz_name.toLowerCase() == searchTerm)
);
});
}
The main difference here is that the function will only return false if it doesn't match any. Before it returned false immediately if it didn't match one of the filter checks.
There's definitely some optimisation you can do to make this more comprehensible. But it illustrates the idea. Hope that helps

Angular 6 *ngFor display different styles for first, odd, even and last

I am learning Angular 6 and just trying to put togheter some of the stuff I have learned and I am currently running into an issue that I cannot find an answer to. I am trying to change the style of a LI using *ngFor depending if the index is First, Last, Odd or Even. So far everything works but I can't figure out how to do it for the Last because everything I add a new object to my list, it is obviously the last so it render the color for the last.
I understand how to do it but the real problem is that I am adding stuff dynamicly to my list from a form and I'm not sure how to evaluate the Last so that the others become to right color.
Keep in mind that I am still a newb and it might look messy and I also understand that some client-side validations I am doing are probably not optimal or required since HTMl5 but I made it to learn.
Here is my code for my component HTML
>
<h1>List of courses :</h1><br>
<div *ngIf="courses.length > 0; then coursesList else noCourses"></div>
<ng-template #coursesList>
<h2>List of Courses :</h2>
<ul *ngFor="let course of courses; index as i;">
<li [ngStyle]="{'background-color':getColor(i)}" style="color: white;">
<strong>Index : </strong>{{i}} <strong>ID : </strong>{{course.id}} <strong>Name</strong> : {{course.name}}
<button (click)="onRemove(i)">Remove</button>
<button (click)="onModify(i)">Modify</button>
</li>
</ul>
</ng-template>
<ng-template #noCourses>
<h5>There are no courses in this list. Use the form bellow to add some.</h5>
</ng-template>
<div (keyup.enter)="onAdd()">
<span>ID : <input type="number" (keypress)="checkNumber($event)" [(ngModel)]="fields.id" placeholder="Enter an ID"></span>
<span>Name : <input type="text" [(ngModel)]="fields.name" placeholder="Enter a NAME"></span>
<button (click)="onAdd()">Add</button>
<button (click)="onClear()">Clear</button>
</div>
<div *ngIf="isNotNumber" style="background-color: red; color:black"><strong>ID can only be numbers !</strong></div>
<div *ngIf="noValues" style="background-color: red; color:black"><strong>Please fill all fields !</strong></div>
<div *ngIf="noModifyValues" style="background-color: red; color:black"><strong>To modify enter all informations!</strong></div>
Code for .TS
>
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
noValues: boolean;
noModifyValues: boolean;
isNotNumber: boolean;
fields: Courses = {id: null, name: null};
courses: Array<Courses> = [];
viewMode: string = null;
checkNumber($event) {
if ($event.keyCode != 13) {
isFinite($event.key) ? this.isNotNumber = false : this.isNotNumber = true;
}
}
onAdd() {
if (!this.fields.id || !this.fields.name) {
this.noValues = true;
} else {
this.courses.push({id: this.fields.id, name: this.fields.name});
this.fields.id = null;
this.fields.name = null;
this.noValues = false;
}
}
onRemove(i) {
this.courses.splice(i, 1);
}
onClear() {
this.courses = [];
this.fields.id = null;
this.fields.name = null;
this.noValues = false;
}
onModify(i) {
if (!this.fields.id || !this.fields.name) {
this.noModifyValues = true;
} else {
this.courses[i].name = this.fields.name;
this.courses[i].id = this.fields.id;
this.noModifyValues = false;
}
}
getColor(i){
if (i % 2 === 0 && i != 0){i = 'odd';}
switch (i) {
case i = 0 : return 'orange';
case i = 'odd' : return 'blue';
}
return 'red';
}
}
interface Courses {
id: number;
name: string;
}
Image of the code in action for better understanding.
If you only want change the background-color you can use [style.background-color] and you can use ternary operator in the .html
<ul *ngFor="let course of courses; let index=i;
let odd=odd;
let last=last;
let first=first">
<li [style.backgound-color]="first?'orange':last?'purple':odd?'blue':'red'">
...
</li>
</ul>
Try something like this
getColor(i){
if (i % 2 === 0 && i != 0){i = 'odd';}
if (this.courses && (this.courses.length - 1 === i)) {i = 'last'}
switch (i) {
case i = 0 : return 'orange';
case i = 'odd' : return 'blue';
}
return 'red';
}
Hope it works - Happy coding !!
Thanks Rahul. The part I was missing is evaluating if there is something in courses. However, I had to had a few more lines to Odd and Last as follow :
getColor(i){
if (this.courses && i != 0 && (this.courses.length - 1 === i)) {i = 'last'}
if (i % 2 === 0 && i != 0 && i != 'last'){i = 'odd';}
switch (i) {
case i = 0 : return 'orange';
case i = 'odd' : return 'blue';
case i = 'last' : return 'purple';
}
return 'red';
}
Quick question. It seems like a whole lot of IF and && and checking specific things. Is that the way to do it properly?
You could use if else ladder instead of mixing up if else and switch and assignments like given below
getColor(i)
{
if(this.courses)
{
if(i==0)
return "orange";
else if(i==this.courses.length-1)
return "purple";
else if (i%2==0)
return "red";
else
return "blue";
}
}

How to sort / order array in Ionic 2? [duplicate]

This question already has answers here:
Sorting an array of objects by property values
(35 answers)
Closed 5 years ago.
I have a page with one list from a JSON file. I want to add a sort button functionality. Below is my code of .ts , .html and picture of console.log of the array.
home.ts code:
this._servall.myservice(this.taskdesc).subscribe(data => {
console.log(data);
this.displaylist = data;
home.html code:
<ion-list no-lines *ngFor="let list of displaylist;let i=index;" >
<ion-item>
<p>{{list.TASKDESC}}</p>
<p>{{list.PRIMARY}}</p>
<p>{{list.DEADLINE_DT}}</p>
</ion-item>
</ion-list>
I want to sort the list with list.PRIMARY //this is a name
I want to sort the list with list.DEADLINE_DT // in increasing order, this is date
Below is the console.log image of this.displaylist array
Create one pipe for sorting
orderbypipe.ts
/*
* Example use
* Basic Array of single type: *ngFor="#todo of todoService.todos | orderBy : '-'"
* Multidimensional Array Sort on single column: *ngFor="#todo of todoService.todos | orderBy : ['-status']"
* Multidimensional Array Sort on multiple columns: *ngFor="#todo of todoService.todos | orderBy : ['status', '-title']"
*/
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'order', pure: false })
export class OrderBy implements PipeTransform {
static _orderByComparator(a: any, b: any): number {
if ((isNaN(parseFloat(a)) || !isFinite(a)) || (isNaN(parseFloat(b)) || !isFinite(b))) {
//Isn't a number so lowercase the string to properly compare
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
}
else {
//Parse strings as numbers to compare properly
if (parseFloat(a) < parseFloat(b)) return -1;
if (parseFloat(a) > parseFloat(b)) return 1;
}
return 0; //equal each other
}
transform(input: any, [config = '+']): any {
if (!Array.isArray(input)) return input;
if (!Array.isArray(config) || (Array.isArray(config) && config.length == 1)) {
var propertyToCheck: string = !Array.isArray(config) ? config : config[0];
var desc = propertyToCheck.substr(0, 1) == '-';
//Basic array
if (!propertyToCheck || propertyToCheck == '-' || propertyToCheck == '+') {
return !desc ? input.sort() : input.sort().reverse();
}
else {
var property: string = propertyToCheck.substr(0, 1) == '+' || propertyToCheck.substr(0, 1) == '-'
? propertyToCheck.substr(1)
: propertyToCheck;
return input.sort(function(a: any, b: any) {
return !desc
? OrderBy._orderByComparator(a[property], b[property])
: -OrderBy._orderByComparator(a[property], b[property]);
});
}
}
else {
//Loop over property of the array in order and sort
return input.sort(function(a: any, b: any) {
for (var i: number = 0; i < config.length; i++) {
var desc = config[i].substr(0, 1) == '-';
var property = config[i].substr(0, 1) == '+' || config[i].substr(0, 1) == '-'
? config[i].substr(1)
: config[i];
var comparison = !desc
? OrderBy._orderByComparator(a[property], b[property])
: -OrderBy._orderByComparator(a[property], b[property]);
//Don't return 0 yet in case of needing to sort by next property
if (comparison != 0) return comparison;
}
return 0; //equal each other
});
}
}
}
Html
<ion-list no-lines *ngFor="let list of displaylist | order : ['+PRIMARY'];let i=index;" >
For more details see this http://www.fueltravel.com/blog/migrating-from-angular-1-to-2-part-1-pipes/

Can I filter an ng-repeat by more than one item at a time?

I have this ng-repeat:
ng-repeat="row in phs.phrasesView =
(phs.phrases | keywordRange:phs.englishRange )"
Here's the code for the filter:
app.filter('keywordRange', function () {
return function (value, english) {
var out = [];
if (!english) {
return value;
}
var ucEnglish = english[0].toUpperCase() + english.substr(1);
value.map((row: any) => {
if (typeof row.english !== 'undefined' &&
typeof row.english === 'string' &&
row.english != null &&
(row.english.includes(english) || row.english.includes(ucEnglish))
) {
out.push(row);
}
});
return out;
};
});
What I would like to do is to extend this so that rows that contain:
phs.englishRange or phs.romajiRange
Are passed through the filter.
Note that only one of englishRange or romajiRange will be populated. There won't be a case where they both have a value.
Can someone tell me is this possible and give me some advice on how I can do this?

Why is my custom filter predicate filter not working

I have an use case wherein I have to filter an array based on selection from two dropdowns. Both the selections are from md-select (from Angular material) hence both the selection will be an array. Any record from the original array that matches any of the record from the selection arrays should be returned by filter.
I have returned the following logic, but I can't figure out why my data do not get filtered.
$scope.filterTasks = function (t, i, array) {
if ($scope.filter.tradeDir.length === 0 && $scope.filter.cluster.length === 0) {
return true;
}
else if ($scope.filter.tradeDir.length !== 0 && $scope.filter.cluster.length === 0) {
$scope.filter.tradeDir.forEach(function (td) {
if ((t.Trade.code === td.trade.code) && (t.Direction.code === td.direction.code)) {
return true;
}
});
} else if ($scope.filter.cluster.length !== 0 && $scope.filter.tradeDir.length !== 0) {
$scope.filter.tradeDir.forEach(function (td) {
if ((t.Trade.code === td.trade.code) && (t.Direction.code === td.direction.code)) {
$scope.filter.cluster.forEach(function(c) {
if (t.Cluster.code === c.code) {
return true;
}
});
}
});
}
else {
return false;
}
}
Surprisingly, when I debug, I can see the control going till the return statement for each matched record. Still, the data does not get filtered. I am puzzled why?
Below is my html code for md-selects:
<div class="filter layout layout-sm-column task_top_filters">
<md-input-container ng-class="{'md-input-has-value': filter.tradeDir}"
class="flex-sm-100 md_container_fr_task">
<label>Trade/Direction</label>
<md-select multiple ng-model="filter.tradeDir"
md-on-close="closeTradeFilter(filter.tradeDir)"
ng-model-options="{trackBy: '$value.name'}">
<md-option ng-value="t" ng-repeat="t in tradeDirArray track by $index">
{{t.name}}
</md-option>
</md-select>
</md-input-container>
<md-input-container ng-disabled="filter.tradeDir.length === 0"
ng-class="{'md-input-has-value': filter.cluster}"
class="flex-sm-100 md_container_fr_task">
<label>Cluster</label>
<md-select multiple ng-model="filter.cluster"
ng-model-options="{trackBy: '$value.code'}">
<md-option ng-value="t" ng-repeat="t in filterClusters track by $index">
{{t.code}}
</md-option>
</md-select>
</md-input-container>
</div>
and here is how I am calling it:
<li ng-repeat="t in dataList| filter: filterTasks track by t.id" class="li_row">
Is there something wrong with the filter? Any help will be appreciated.
Well, the issue with above code was that I was returning true for forEach function, which instead should be returned from the original filterTasks function.
Below code works fine:
$scope.filterTasks = function (t, i, array) {
if ($scope.filter.tradeDir.length === 0 && $scope.filter.cluster.length === 0) {
return true;
} else if ($scope.filter.tradeDir.length !== 0 && $scope.filter.cluster.length === 0) {
$scope.filter.tradeDir.forEach(function (td) {
if ((t.Trade.code === td.trade.code) && (t.Direction.code === td.direction.code)) {
x = true;
}
});
return x;
} else if ($scope.filter.cluster.length !== 0 && $scope.filter.tradeDir.length !== 0) {
$scope.filter.tradeDir.forEach(function (td) {
if ((t.Trade.code === td.trade.code) && (t.Direction.code === td.direction.code)) {
$scope.filter.cluster.forEach(function (c) {
if (t.Cluster.code === c.code) {
x = true;
}
});
return x;
}
});
return x;
} else {
return false;
}
}

Resources