I am trying to use a custom pipe to filter my *ngFor
loop using an input field with ngModel. With my other custom pipe (sortBy), it works perfectly fine. Howeve
I know this is old, but i think i have good solution. Comparing to other answers and also comparing to accepted, mine accepts multiple values. Basically filter object with key:value search parameters (also object within object). Also it works with numbers etc, cause when comparing, it converts them to string.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'filter'})
export class Filter implements PipeTransform {
transform(array: Array<Object>, filter: Object): any {
let notAllKeysUndefined = false;
let newArray = [];
if(array.length > 0) {
for (let k in filter){
if (filter.hasOwnProperty(k)) {
if(filter[k] != undefined && filter[k] != '') {
for (let i = 0; i < array.length; i++) {
let filterRule = filter[k];
if(typeof filterRule === 'object') {
for(let fkey in filterRule) {
if (filter[k].hasOwnProperty(fkey)) {
if(filter[k][fkey] != undefined && filter[k][fkey] != '') {
if(this.shouldPushInArray(array[i][k][fkey], filter[k][fkey])) {
newArray.push(array[i]);
}
notAllKeysUndefined = true;
}
}
}
} else {
if(this.shouldPushInArray(array[i][k], filter[k])) {
newArray.push(array[i]);
}
notAllKeysUndefined = true;
}
}
}
}
}
if(notAllKeysUndefined) {
return newArray;
}
}
return array;
}
private shouldPushInArray(item, filter) {
if(typeof filter !== 'string') {
item = item.toString();
filter = filter.toString();
}
// Filter main logic
item = item.toLowerCase();
filter = filter.toLowerCase();
if(item.indexOf(filter) !== -1) {
return true;
}
return false;
}
}
Pipes in Angular 2+ are a great way to transform and format data right from your templates.
Pipes allow us to change data inside of a template; i.e. filtering, ordering, formatting dates, numbers, currencies, etc. A quick example is you can transfer a string to lowercase by applying a simple filter in the template code.
List of Built-in Pipes from API List Examples
{{ user.name | uppercase }}
Example of Angular version 4.4.7. ng version
Custom Pipes which accepts multiple arguments.
HTML « *ngFor="let student of students | jsonFilterBy:[searchText, 'name'] "
TS « transform(json: any[], args: any[]) : any[] { ... }
Filtering the content using a Pipe « json-filter-by.pipe.ts
import { Pipe, PipeTransform, Injectable } from '@angular/core';
@Pipe({ name: 'jsonFilterBy' })
@Injectable()
export class JsonFilterByPipe implements PipeTransform {
transform(json: any[], args: any[]) : any[] {
var searchText = args[0];
var jsonKey = args[1];
// json = undefined, args = (2) [undefined, "name"]
if(searchText == null || searchText == 'undefined') return json;
if(jsonKey == null || jsonKey == 'undefined') return json;
// Copy all objects of original array into new Array.
var returnObjects = json;
json.forEach( function ( filterObjectEntery ) {
if( filterObjectEntery.hasOwnProperty( jsonKey ) ) {
console.log('Search key is available in JSON object.');
if ( typeof filterObjectEntery[jsonKey] != "undefined" &&
filterObjectEntery[jsonKey].toLowerCase().indexOf(searchText.toLowerCase()) > -1 ) {
// object value contains the user provided text.
} else {
// object didn't match a filter value so remove it from array via filter
returnObjects = returnObjects.filter(obj => obj !== filterObjectEntery);
}
} else {
console.log('Search key is not available in JSON object.');
}
})
return returnObjects;
}
}
Add to @NgModule
« Add JsonFilterByPipe
to your declarations list in your module; if you forget to do this you'll get an error no provider for jsonFilterBy
. If you add to module then it is available to all the component's of that module.
@NgModule({
imports: [
CommonModule,
RouterModule,
FormsModule, ReactiveFormsModule,
],
providers: [ StudentDetailsService ],
declarations: [
UsersComponent, UserComponent,
JsonFilterByPipe,
],
exports : [UsersComponent, UserComponent]
})
export class UsersModule {
// ...
}
File Name: users.component.ts
and StudentDetailsService
is created from this link.
import { MyStudents } from './../../services/student/my-students';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { StudentDetailsService } from '../../services/student/student-details.service';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: [ './users.component.css' ],
providers:[StudentDetailsService]
})
export class UsersComponent implements OnInit, OnDestroy {
students: MyStudents[];
selectedStudent: MyStudents;
constructor(private studentService: StudentDetailsService) { }
ngOnInit(): void {
this.loadAllUsers();
}
ngOnDestroy(): void {
// ONDestroy to prevent memory leaks
}
loadAllUsers(): void {
this.studentService.getStudentsList().then(students => this.students = students);
}
onSelect(student: MyStudents): void {
this.selectedStudent = student;
}
}
File Name: users.component.html
<div>
<br />
<div class="form-group">
<div class="col-md-6" >
Filter by Name:
<input type="text" [(ngModel)]="searchText"
class="form-control" placeholder="Search By Category" />
</div>
</div>
<h2>Present are Students</h2>
<ul class="students">
<li *ngFor="let student of students | jsonFilterBy:[searchText, 'name'] " >
<a *ngIf="student" routerLink="/users/update/{{student.id}}">
<span class="badge">{{student.id}}</span> {{student.name | uppercase}}
</a>
</li>
</ul>
</div>
Here is a working plunkr with a filter and sortBy pipe. https://plnkr.co/edit/vRvnNUULmBpkbLUYk4uw?p=preview
As developer033 mentioned in a comment, you are passing in a single value to the filter pipe, when the filter pipe is expecting an array of values. I would tell the pipe to expect a single value instead of an array
export class FilterPipe implements PipeTransform {
transform(items: any[], term: string): any {
// I am unsure what id is here. did you mean title?
return items.filter(item => item.id.indexOf(term) !== -1);
}
}
I would agree with DeborahK that impure pipes should be avoided for performance reasons. The plunkr includes console logs where you can see how much the impure pipe is called.
The transform method signature changed somewhere in an RC of Angular 2. Try something more like this:
export class FilterPipe implements PipeTransform {
transform(items: any[], filterBy: string): any {
return items.filter(item => item.id.indexOf(filterBy) !== -1);
}
}
And if you want to handle nulls and make the filter case insensitive, you may want to do something more like the one I have here:
export class ProductFilterPipe implements PipeTransform {
transform(value: IProduct[], filterBy: string): IProduct[] {
filterBy = filterBy ? filterBy.toLocaleLowerCase() : null;
return filterBy ? value.filter((product: IProduct) =>
product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1) : value;
}
}
And NOTE: Sorting and filtering in pipes is a big issue with performance and they are NOT recommended. See the docs here for more info: https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe