ChatGPT解决这个技术问题 Extra ChatGPT

How to apply filters to *ngFor?

Apparently, Angular 2 will use pipes instead of filters as in Angular1 in conjunction with ng-for to filter results, although the implementation still seems to be vague, with no clear documentation.

Namely what I'm trying to achieve could be viewed from the following perspective

<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>

How to implement so using pipes?

Note that a breaking change is introduced in beta 17 for ngFor regarding the hash symbol. The correct way is: <div *ngFor="let item of itemsList" *ngIf="conditon(item)" ...
@MemetOlsen comment from Gunter below: "*ngFor and *ngIf on the same element are not supported. You need to change to the explicit form for one of them"
Even tho it's what the OP asks for, it's recommanded to NOT USE PIPE for filtering or ordering in Angular2+. Prefer having a class property with the filtered values : angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe

M
Michael

Basically, you write a pipe which you can then use in the *ngFor directive.

In your component:

filterargs = {title: 'hello'};
items = [{title: 'hello world'}, {title: 'hello kitty'}, {title: 'foo bar'}];

In your template, you can pass string, number or object to your pipe to use to filter on:

<li *ngFor="let item of items | myfilter:filterargs">

In your pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'myfilter',
    pure: false
})
export class MyFilterPipe implements PipeTransform {
    transform(items: any[], filter: Object): any {
        if (!items || !filter) {
            return items;
        }
        // filter items array, items which match and return true will be
        // kept, false will be filtered out
        return items.filter(item => item.title.indexOf(filter.title) !== -1);
    }
}

Remember to register your pipe in app.module.ts; you no longer need to register the pipes in your @Component

import { MyFilterPipe } from './shared/pipes/my-filter.pipe';

@NgModule({
    imports: [
        ..
    ],
    declarations: [
        MyFilterPipe,
    ],
    providers: [
        ..
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Here's a Plunker which demos the use of a custom filter pipe and the built-in slice pipe to limit results.

Please note (as several commentators have pointed out) that there is a reason why there are no built-in filter pipes in Angular.


Thanx, this work as intended, but sometimes it's better to check if the items array is defined and not null, because Ng2 may try to apply filter while the "items" still undefined.
In addition, I needed to add the filter class to the @Component declaration. Like So: @Component({... pipes: [MyFilterPipe ]
I think it also needs this line ìf (!items) return items;` in case the array is empty.
Angular says using a Pipe has performing issues, so recommends to make filtering on the component
I'd like to suggest to wrap *ngFor parameters in parenthesis, just to avoid any confusion and make it "change-proof": <li *ngFor="let item of (items | myfilter:filterargs)">
S
Slava Fomin II

A lot of you have great approaches, but the goal here is to be generic and defined a array pipe that is extremely reusable across all cases in relationship to *ngFor.

callback.pipe.ts (don't forget to add this to your module's declaration array)

import { PipeTransform, Pipe } from '@angular/core';

@Pipe({
    name: 'callback',
    pure: false
})
export class CallbackPipe implements PipeTransform {
    transform(items: any[], callback: (item: any) => boolean): any {
        if (!items || !callback) {
            return items;
        }
        return items.filter(item => callback(item));
    }
}

Then in your component, you need to implement a method with the following signuature (item: any) => boolean, in my case for example, I called it filterUser, that filters users' age that are greater than 18 years.

Your Component

@Component({
  ....
})
export class UsersComponent {
  filterUser(user: IUser) {
    return !user.age >= 18
  }
}

And last but not least, your html code will look like this:

Your HTML

<li *ngFor="let user of users | callback: filterUser">{{user.name}}</li>

As you can see, this Pipe is fairly generic across all array like items that need to be filter via a callback. In mycase, I found it to be very useful for *ngFor like scenarios.

Hope this helps!!!

codematrix


I notice that in the function filterUser() - or my equivalent function to that - you can't use "this" to access the current component instance like you can with all other functions in the component class. I need to access the component object to check the filtered item is in a collection.
@code5 yesterday, well when i tried to access it it didnt work. It said this is undefined.
@Paul, hmm... that's impossible. Is your method private? Not that should matter as privates are only compile constructs and are not enforced at runtime. In my example I used IUser. This assumes that the items in the collection being iterated map to it. You can try any to see if it works. Also, make sure the name is typed correct, case and all.
To avoid the issue of this being undefined, you can write your method on your component like filterUser = (user: IUser) => rather than filteruser(user: IUser)
@Paul I know this is too late to help you, but it might help others. The reason you were losing this on your component method is because the method was being used as a callback and a new this context was applied. You ran into a common problem in object oriented javascript, but there's an old and easy solution: you bind methods to be used as callbacks to the original class. In your constructor, add the following code: this.myCallbackFunc = this.myCallbackFunc.bind(this); That's it. You'll never lose this again.
R
Rodolfo Jorge Nemer Nogueira

Simplified way (Used only on small arrays because of performance issues. In large arrays you have to make the filter manually via code):

See: https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe

@Pipe({
    name: 'filter'
})
@Injectable()
export class FilterPipe implements PipeTransform {
    transform(items: any[], field : string, value : string): any[] {  
      if (!items) return [];
      if (!value || value.length == 0) return items;
      return items.filter(it => 
      it[field].toLowerCase().indexOf(value.toLowerCase()) !=-1);
    }
}

Usage:

<li *ngFor="let it of its | filter : 'name' : 'value or variable'">{{it}}</li>

If you use a variable as a second argument, don't use quotes.


Maybe add following to show how to combine it with ReqExp: return items.filter( item => { return new RegExp(value, "i").test(item[field]) });
According to the Angular team, this is considered bad practice.
@torazaburo can you reference their opinion or explain why? Thanks
According to the Angular team, this is poor code because it is slow and it isn't minified well. The team doesn't want to see slow websites due to their code so they didn't build it into Angular this time. angular.io/docs/ts/latest/guide/…
S
Siegen

This is what I implemented without using pipe.

component.html

<div *ngFor="let item of filter(itemsList)">

component.ts

@Component({
....
})
export class YourComponent {
  filter(itemList: yourItemType[]): yourItemType[] {
    let result: yourItemType[] = [];
    //your filter logic here
    ...
    ...
    return result;
  }
}

I think this would be computationally intensive because Angular will execute the filter every time it runs change detection. It will not scale well to large arrays. A cleaner, though more complex to code, solution would be to make itemList an Observable and use the async filter: let item of itemsList | async. When a change occurs, make the observable emit the new list. This way, the filtering code is only run when needed.
This answer should have a negative score. It's bad, use a pipe.
I'm not sure I understand why this is too bad, doesn't a pipe or anything else have to potentially filter out during change detection anyway, regardless of what you use? If you put a breakpoint in the pipe, you'll see it still runs on each change detection.. How is the observable method any better than trackBy, since still, at the end of the day, it needs to filter based on the value of a variable that could have changed..? You could use a separate list and update, then push changes also.
M
Mark

I'm not sure when it came in but they already made slice pipe that will do that. It's well documented too.

https://angular.io/docs/ts/latest/api/common/index/SlicePipe-pipe.html

<p *ngFor="let feature of content?.keyFeatures | slice:1:5">
   {{ feature.description }}
</p>

If you use the trackBy Interface the slice pipe must applied before the ;. e.g.: *ngFor="let feature of content?.keyFeatures | slice:1:5; trackBy feature?.id"
M
Michael V

A simple solution that works with Angular 6 for filtering a ngFor, it's the following:

your code

Spans are useful because does not inherently represent anything.


better than is to use as it won't add any unnecessary markup which in addition to the html noise could affect your CSS.
I'm such a nerd that this made me literally laugh out loud, I think due to the unexpected diversion from normally recommended logic filtering the ngFor. Inside-out Russian doll, but still looks the same as before? Does anyone know if this comes out the same, better, or worse than filtering on the ngFor? Really curious!
Functions should not be used in templates
J
Jeroen

You could also use the following:

<template ngFor let-item [ngForOf]="itemsList">
    <div *ng-if="conditon(item)"></div>
</template>

This will only show the div if your items matches the condition

See the angular documentation for more information If you would also need the index, use the following:

<template ngFor let-item [ngForOf]="itemsList" let-i="index">
    <div *ng-if="conditon(item, i)"></div>
</template>

Won't this enter the template for every item in the list instead of just the filtered list? That could be a performance hit.
B
Ben Glasser

pipes in Angular2 are similar to pipes on the command line. The output of each preceding value is fed into the filter after the pipe which makes it easy to chain filters as well like this:

<template *ngFor="#item of itemsList">
    <div *ngIf="conditon(item)">{item | filter1 | filter2}</div>
</template>

Sorry if this was misleading, my point here is the variable item from *ng-for="#item of itemsList" should be used to filter results as such *ng-if="conditon(item)". Which doesn't work in this example..
you could make condition a filter and do the same thing with {{item | condition}} condition would just return item if the condition is met and no value if it is not.
@BenGlasser I thought pipes were applied from right-to-left. So this would apply filter2 first, then filter1.
*ngFor and *ngIf on the same element are not supported. You need to change to the explicit form for one of them <template ngFor ...>
@GünterZöchbauer It took me a year, but I've updated the syntax to reflect the changes you suggested
t
tgralex

I know its an old question, however, I thought it might be helpful to offer another solution.

equivalent of AngularJS of this

<div *ng-for="#item of itemsList" *ng-if="conditon(item)"></div>

in Angular 2+ you cant use *ngFor and *ngIf on a same element, so it will be following:

<div *ngFor="let item of itemsList">
     <div *ngIf="conditon(item)">
     </div>
</div>

and if you can not use as internal container use ng-container instead. ng-container is useful when you want to conditionally append a group of elements (ie using *ngIf="foo") in your application but don't want to wrap them with another element.


B
BlackSlash

There is a dynamic filter pipe that I use

Source data:

items = [{foo: 'hello world'}, {foo: 'lorem ipsum'}, {foo: 'foo bar'}];

In the template you can dinamically set the filter in any object attr:

<li *ngFor="let item of items | filter:{foo:'bar'}">

The pipe:

  import { Pipe, PipeTransform } from '@angular/core';

  @Pipe({
    name: 'filter',
  })
  export class FilterPipe implements PipeTransform {
    transform(items: any[], filter: Record<string, any>): any {
      if (!items || !filter) {
        return items;
      }

      const key = Object.keys(filter)[0];
      const value = filter[key];

      return items.filter((e) => e[key].indexOf(value) !== -1);
    }
  }

Don't forget to register the pipe in your app.module.ts declarations


W
Wedson Quintanilha da Silva

For this requirement, I implement and publish a generic component. See

https://www.npmjs.com/package/w-ng5

For use this components, before, install this package with npm:

npm install w-ng5 --save

After, import module in app.module

...
import { PipesModule } from 'w-ng5';

In the next step, add in declare section of app.module:

imports: [
  PipesModule,
  ...
]

Sample use

Filtering simple string

<input type="text"  [(ngModel)]="filtroString">
<ul>
  <li *ngFor="let s of getStrings() | filter:filtroString">
    {{s}}
  </li>
</ul>

Filtering complex string - field 'Value' in level 2

<input type="text"  [(ngModel)]="search">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtering complex string - middle field - 'Value' in level 1

<input type="text"  [(ngModel)]="search3">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.valor1', value: search3}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtering complex array simple - field 'Nome' level 0

<input type="text"  [(ngModel)]="search2">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'nome', value: search2}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtering in tree fields - field 'Valor' in level 2 or 'Valor' in level 1 or 'Nome' in level 0

<input type="text"  [(ngModel)]="search5">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.valor2', value: search5}, {field:'n1.valor1', value: search5}, {field:'nome', value: search5}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

Filtering nonexistent field - 'Valor' in nonexistent level 3

<input type="text"  [(ngModel)]="search4">
<ul>
  <li *ngFor="let s of getComplexTypesExtends() | filter:[{field:'n1.n2.n3.valor3', value: search4}]">
    {{s.nome}} - {{s.idade}} - {{s.n1.valor1}} - {{s.n1.n2.valor2}}
  </li>
</ul>

This component work with infinite attribute level...


Hi, I'm here and I've followed all the steps and in this case I'm using this *ngFor="let inovice of invoices | filter:searchInvoice" and it's searching on my list, but shows a blank list, do you know why?
Hello, please tell me what is the structure and type of objects that your list of invoices contains. The way you are using it should only be applied if your invoices list is of type string. If you want to search by invoice number (invoice.number), use this: *ngFor = "let inovice of invoices | filter: {field: number, value: searchInvoice}". If you want to filter by two columns, for example, invoice.customer.name, use: *ngFor = "let inovice of invoices | filter: [field: number, value: searchInvoice}, {field: customer.name, value: searchInvoice} ].
H
Hardik Patel

Pipe would be best approach. but below one would also work.

<div *ng-for="#item of itemsList">
  <ng-container *ng-if="conditon(item)">
    // my code
  </ng-container>
</div>

this can break certain things. for example inside a mat-form-field
N
Nate May

I've created a plunker based off of the answers here and elsewhere.

Additionally I had to add an @Input, @ViewChild, and ElementRef of the <input> and create and subscribe() to an observable of it.

Angular2 Search Filter: PLUNKR (UPDATE: plunker no longer works)


B
Blablalux

Based on the very elegant callback pipe solution proposed above, it is possible to generalize it a bit further by allowing additional filter parameters to be passed along. We then have :

callback.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'callback',
  pure: false
})
export class CallbackPipe implements PipeTransform {
  transform(items: any[], callback: (item: any, callbackArgs?: any[]) => boolean, callbackArgs?: any[]): any {
    if (!items || !callback) {
      return items;
    }
    return items.filter(item => callback(item, callbackArgs));
  }
}

component

filterSomething(something: Something, filterArgs: any[]) {
  const firstArg = filterArgs[0];
  const secondArg = filterArgs[1];
  ...
  return <some condition based on something, firstArg, secondArg, etc.>;
}

html

<li *ngFor="let s of somethings | callback : filterSomething : [<whatWillBecomeFirstArg>, <whatWillBecomeSecondArg>, ...]">
  {{s.aProperty}}
</li>

Great idea, clapping 👏
P
Pàldi Gergő

This is my code:

import {Pipe, PipeTransform, Injectable} from '@angular/core';

@Pipe({
    name: 'filter'
})
@Injectable()
export class FilterPipe implements PipeTransform {
    transform(items: any[], field : string, value): any[] {
      if (!items) return [];
      if (!value || value.length === 0) return items;
      return items.filter(it =>
      it[field] === value);
    }
}

Sample:

LIST = [{id:1,name:'abc'},{id:2,name:'cba'}];
FilterValue = 1;

<span *ngFor="let listItem of LIST | filter : 'id' : FilterValue">
                              {{listItem .name}}
                          </span>

R
Rick Strahl

Another approach I like to use for application specific filters, is to use a custom read-only property on your component which allows you to encapsulate the filtering logic more cleanly than using a custom pipe (IMHO).

For example, if I want to bind to albumList and filter on searchText:

searchText: "";
albumList: Album[] = [];

get filteredAlbumList() {
    if (this.config.searchText && this.config.searchText.length > 1) {
      var lsearchText = this.config.searchText.toLowerCase();
      return this.albumList.filter((a) =>
        a.Title.toLowerCase().includes(lsearchText) ||
        a.Artist.ArtistName.toLowerCase().includes(lsearchText)
      );
    }
    return this.albumList;
}

To bind in the HTML you can then bind to the read-only property:

<a class="list-group-item"
       *ngFor="let album of filteredAlbumList">
</a>

I find for specialized filters that are application specific this works better than a pipe as it keeps the logic related to the filter with the component.

Pipes work better for globally reusable filters.


Won't this method trigger continuous dirty checkings instead of using a valueChanged approach?
S
Sanchit Tandon

I created the following pipe for getting desired items from a list.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

  transform(items: any[], filter: string): any {
    if(!items || !filter) {
      return items;
    }
    // To search values only of "name" variable of your object(item)
    //return items.filter(item => item.name.toLowerCase().indexOf(filter.toLowerCase()) !== -1);

    // To search in values of every variable of your object(item)
    return items.filter(item => JSON.stringify(item).toLowerCase().indexOf(filter.toLowerCase()) !== -1);
  }

}

Lowercase conversion is just to match in case insensitive way. You can use it in your view like this:-

<div>
  <input type="text" placeholder="Search reward" [(ngModel)]="searchTerm">
</div>
<div>
  <ul>
    <li *ngFor="let reward of rewardList | filter:searchTerm">
      <div>
        <img [src]="reward.imageUrl"/>
        <p>{{reward.name}}</p>
      </div>
    </li>
  </ul>
</div>

P
Peter Huang

Ideally you should create angualr 2 pipe for that. But you can do this trick.

<ng-container *ngFor="item in itemsList">
    <div*ngIf="conditon(item)">{{item}}</div>
</ng-container>

l
long2know

Here's an example that I created a while back, and blogged about, that includes a working plunk. It provides a filter pipe that can filter any list of objects. You basically just specify the property and value {key:value} within your ngFor specification.

It's not a lot different from @NateMay's response, except that I explain it in relatively verbose detail.

In my case, I filtered an unordered list on some text (filterText) the user entered against the "label" property of the objects in my array with this sort of mark-up:

<ul>
  <li *ngFor="let item of _items | filter:{label: filterText}">{{ item.label }}</li>
</ul>

https://long2know.com/2016/11/angular2-filter-pipes/


P
Piotr Rogowski

The first step you create Filter using @Pipe in your component.ts file:

your.component.ts

import { Component, Pipe, PipeTransform, Injectable } from '@angular/core';
import { Person} from "yourPath";

@Pipe({
  name: 'searchfilter'
})
@Injectable()
export class SearchFilterPipe implements PipeTransform {
  transform(items: Person[], value: string): any[] {
    if (!items || !value) {
      return items;
    }
    console.log("your search token = "+value);
    return items.filter(e => e.firstName.toLowerCase().includes(value.toLocaleLowerCase()));
  }
}
@Component({
  ....
    persons;

    ngOnInit() {
         //inicial persons arrays
    }
})

And data structure of Person object:

person.ts

export class Person{
    constructor(
        public firstName: string,
        public lastName: string
    ) { }
}

In your view in html file:

your.component.html

    <input class="form-control" placeholder="Search" id="search" type="text" [(ngModel)]="searchText"/>
    <table class="table table-striped table-hover">
      <colgroup>
        <col span="1" style="width: 50%;">
        <col span="1" style="width: 50%;">
      </colgroup>
      <thead>
        <tr>
          <th>First name</th>
          <th>Last name</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let person of persons | searchfilter:searchText">
          <td>{{person.firstName}}</td>
          <td>{{person.lastName}}</td>
        </tr>
      </tbody>
    </table>

G
Gajender Singh

This is your array

products: any = [
        {
            "name": "John-Cena",
                    },
        {
            "name": "Brock-Lensar",

        }
    ];

This is your ngFor loop Filter By :

<input type="text" [(ngModel)]='filterText' />
    <ul *ngFor='let product of filterProduct'>
      <li>{{product.name }}</li>
    </ul>

There I'm using filterProduct instant of products, because i want to preserve my original data. Here model _filterText is used as a input box.When ever there is any change setter function will call. In setFilterText performProduct is called it will return the result only those who match with the input. I'm using lower case for case insensitive.

filterProduct = this.products;
_filterText : string;
    get filterText() : string {
        return this._filterText;
    }

    set filterText(value : string) {
        this._filterText = value;
        this.filterProduct = this._filterText ? this.performProduct(this._filterText) : this.products;

    } 

    performProduct(value : string ) : any {
            value = value.toLocaleLowerCase();
            return this.products.filter(( products : any ) => 
                products.name.toLocaleLowerCase().indexOf(value) !== -1);
        }

a
alindber

After some googling, I came across ng2-search-filter. In will take your object and apply the search term against all object properties looking for a match.


R
Richard Aguirre

https://i.stack.imgur.com/VnazQ.png

i did this Beauty Solution:

filter.pipe.ts

import { PipeTransform, Pipe } from '@angular/core';

@Pipe({
  name: 'filterx',
  pure: false
})
export class FilterPipe implements PipeTransform {
 transform(items: any, filter: any, isAnd: boolean): any {
  let filterx=JSON.parse(JSON.stringify(filter));
  for (var prop in filterx) {
    if (Object.prototype.hasOwnProperty.call(filterx, prop)) {
       if(filterx[prop]=='')
       {
         delete filterx[prop];
       }
    }
 }
if (!items || !filterx) {
  return items;
}

return items.filter(function(obj) {
  return Object.keys(filterx).every(function(c) {
    return obj[c].toLowerCase().indexOf(filterx[c].toLowerCase()) !== -1
  });
  });
  }
}

component.ts

slotFilter:any={start:'',practitionerCodeDisplay:'',practitionerName:''};

componet.html

             <tr>
                <th class="text-center">  <input type="text" [(ngModel)]="slotFilter.start"></th>
                <th class="text-center"><input type="text" [(ngModel)]="slotFilter.practitionerCodeDisplay"></th>
                <th class="text-left"><input type="text" [(ngModel)]="slotFilter.practitionerName"></th>
                <th></th>
              </tr>


 <tbody *ngFor="let item of practionerRoleList | filterx: slotFilter">...

Y
Yisal Khan

Most simple and easy way to limit your ngFor is given below

<li *ngFor="let item of list | slice:0:10; let i=index" class="dropdown-item" >{{item.text}}</li>


H
Hoan Danh

You can do this trick:

<ng-container *ngFor="item in items">
    <div *ngIf="conditon(item)">{{ item.value }}</div>
</ng-container>

or

<div *ngFor="item in items">
  <ng-container *ngIf="conditon(item)">{{ item.value }}</ng-container>
</div>

the main problem with this approach is then index and possibly first and last are no longer reliable values