如何将过滤器应用于* ngFor?


278

显然,Angular 2将使用管道代替Angular1中的ng-for过滤器来过滤结果,尽管实现起来似乎还很模糊,没有明确的文档。

即我可以从以下角度看待我要实现的目标

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

如何使用管道来实现?


8
请注意,针对哈希符号的ngFor在beta 17中引入了重大更改。正确的方法是:<div *ngFor="let item of itemsList" *ngIf="conditon(item)" ...
Memet Olsen 2016年


11
来自Gunter的@MemetOlsen评论如下:“ 不支持*ngFor*ngIf在同一元素上。您需要为其中之一更改为显式形式”
Red Pea 2015年

1
即使这是OP的要求,也建议不要在Angular2 +中使用PIPE进行过滤或排序。最好具有带有过滤值的class属性:angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe
ylerjen

Answers:


395

基本上,您编写了一个管道,然后可以在*ngFor指令中使用它。

在您的组件中:

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

在模板中,您可以将字符串,数字或对象传递给管道以用于过滤:

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

在您的管道中:

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);
    }
}

记住要在其中注册管道app.module.ts;您不再需要在您的管道中注册管道@Component

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

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

这是一个Plunker,它演示了如何使用自定义过滤器管道和内置切片管道来限制结果。

请注意(正如一些评论家所指出的那样),Angular中没有内置过滤器管道是有原因的


6
谢谢,这项工作按预期进行,但是有时最好检查是否定义了items数组,而不是null,因为Ng2可能会尝试在“ items”仍未定义的情况下应用过滤器。
timmz

1
另外,我需要将过滤器类添加到@Component声明中。像这样:@Component({... pipes:[MyFilterPipe]
Stephen

1
我认为在数组为空的情况下,它也需要此行“ f(!items)返回项目”。
波斯蒂安Pišler

2
角说使用管道已执行的问题,所以建议使组件上的过滤
塞巴斯蒂安·罗哈斯

3
我想建议将*ngFor参数包装在括号中,以避免任何混淆并使它“防更改”:<li *ngFor="let item of (items | myfilter:filterargs)">
Tomas,

104

你们中的许多人都有很好的方法,但是这里的目标是通用并定义一个数组管道,该数组管道在与* ngFor相关的所有情况下都可以重用。

callback.pipe.ts(不要忘记将其添加到模块的声明数组中)

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));
    }
}

然后,在您的组件中,您需要实现一个具有以下签名的方法(项目:any)=>布尔值,在我的例子中,我将其称为filterUser,它可以过滤18岁以上的用户年龄。

您的组件

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

最后但并非最不重要的一点是,您的html代码将如下所示:

您的HTML

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

如您所见,此Pipe在需要通过回调过滤的所有数组(如项)中是相当通用的。在mycase中,我发现它对于* ngFor这样的场景非常有用。

希望这可以帮助!!!

码矩阵


4
我注意到在函数filterUser()或与之等效的函数中,您无法像使用组件类中的所有其他函数那样使用“ this”来访问当前组件实例。我需要访问组件对象以检查过滤后的项在集合中。
保罗

1
@Paul,嗯...那是不可能的。您的方法私密吗?没关系,因为私有只是编译构造,并且在运行时不强制执行。在我的示例中,我使用了IUser。这假定要迭代的集合中的项目映射到它。您可以尝试任何一种,看看是否可行。另外,请确保输入正确的名称,大小写和全部。
code5

1
我无法使用此方法访问组件变量
suulisin

10
为避免this出现未定义的问题,您可以像在您的组件上那样编写方法,filterUser = (user: IUser) =>而不是filteruser(user: IUser)
Tom

2
@Paul我知道这对您有所帮助为时已晚,但它可能会帮助其他人。您this在组件方法上迷失的原因是因为该方法被用作回调,并且应用了新的this上下文。您在面向对象的javascript中遇到了一个常见问题,但是有一个古老而又简单的解决方案:将要用作回调的方法绑定到原始类。在构造函数中,添加以下代码:this.myCallbackFunc = this.myCallbackFunc.bind(this); 就是这样。您再也不会输this
Randolpho

36

简化方式(由于性能问题,仅在小型阵列上使用。在大型阵列中,您必须通过代码手动制作过滤器):

参见: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);
    }
}

用法:

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

如果将变量用作第二个参数,请不要使用引号。


3
也许添加以下内容以显示如何将其与ReqExp组合:return items.filter(item => {return new RegExp(value,“ i”)。test(item [field])});
约翰内斯

8
根据Angular团队的说法,这被认为是不好的做法。

@torazaburo您可以参考他们的意见或解释原因吗?谢谢
Zymotik '17


2
根据Angular团队的说法,这是糟糕的代码,因为它运行缓慢且无法很好地缩小。该团队不希望由于其代码而导致网站运行缓慢,因此这次他们没有将其构建到Angular中。angular.io/docs/ts/latest/guide/…–
Zymotik

29

这是我在不使用管道的情况下实现的。

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;
  }
}

16
我认为这会占用大量计算资源,因为Angular每次运行更改检测都会执行过滤器。它不能很好地扩展到大型阵列。较干净的代码(虽然对代码而言更复杂)解决方案是使它itemList成为Observable并使用异步过滤器:let item of itemsList | async。发生更改时,使可观察对象发出新列表。这样,过滤代码仅在需要时运行。
BeetleJuice

1
该答案应为负分。不好,用管子。
Cétia

19

我不确定它什么时候进来的,但是他们已经制造了可以做到这一点的切片管。它也有据可查。

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>

4
如果您使用trackBy接口,则必须在之前应用切片管道;。例如:*ngFor="let feature of content?.keyFeatures | slice:1:5; trackBy feature?.id"
菲利普(Philip)

11

您还可以使用以下内容:

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

仅当您的商品符合条件时才显示div

有关更多信息,请参见角度文档。如果您还需要索引,请使用以下命令:

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

1
这不是为列表中的每个项目输入模板,而不是仅为过滤后的列表输入模板吗?这可能会影响性能。
Azeroth2b

8

Angular2中的管道类似于命令行上的管道。每个先前值的输出在管道之后被馈送到过滤器中,这使得链接过滤器变得容易,如下所示:

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

很抱歉,如果这是误导,在这里我要说的是变量item*ng-for="#item of itemsList"应该使用来过滤结果*ng-if="conditon(item)"。这确实在这个例子不会工作。
哈立德

您可以将条件设​​为过滤器,并使用{{item | item如果满足条件,条件将只返回,否则将不返回任何值。
本格拉斯

@BenGlasser我认为管道是从右到左应用的。因此,这将首先应用filter2,然后应用filter1。
Evan Plaice

12
*ngFor*ngIf不支持在同一元素上。你需要改变,以明确的形式,其中之一<template ngFor ...>
君特Zöchbauer

1
@GünterZöchbauer我花了一年时间,但我更新了语法以反映您建议的更改
Ben Glasser

5

为此,我实现并发布了通用组件。看到

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

要使用此组件,请先使用npm安装此软件包:

npm install w-ng5 --save

之后,在app.module中导入模块

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

在下一步中,添加app.module的声明部分:

imports: [
  PipesModule,
  ...
]

样品使用

过滤简单字符串

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

过滤复杂的字符串-级别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>

过滤复杂的字符串-中间字段-级别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>

简单过滤复杂数组-字段“ Nome”级别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>

在树字段中过滤-级别2中的“ Valor”字段或级别1中的“ Valor”或级别0中的“ Nome”

<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>

过滤不存在​​的字段-级别3中不存在“ Valor”

<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>

此组件可用于无限属性级别...


嗨,我在这里,我已经按照所有步骤进行了操作,在这种情况下,我正在使用它*ngFor="let inovice of invoices | filter:searchInvoice",它正在我的列表中搜索,但显示空白列表,您知道为什么吗?
jecorrales

1
您好,请告诉我您的发票清单包含的对象的结构和类型是什么。 仅当发票清单的类型为字符串时,才应使用它的使用方式。 如果要按发票编号(invoice.number)进行搜索,请使用:* ngFor =“让发票发票|过滤器:{字段:编号,值:searchInvoice}”。如果要按两列进行过滤,例如invoice.customer.name,请使用:* ngFor =“ let inovice of invoices | filter:[field:number,value:searchInvoice},{field:customer.name,value: searchInvoice}]
Wedson Quintanilha达席尔瓦

4

与Angular 6一起使用的用于过滤ngFor的简单解决方案如下:

<span *ngFor="item of itemsList"  >
  <div *ngIf="yourCondition(item)">
    
    your code
    
  </div>
</span

跨度很有用,因为它并不固有地代表任何东西。


1
比<span>更好的方法是使用<ng-container>,因为它不会添加任何不必要的标记,除了html噪声外,这还会影响CSS。
Trevor de Koekkoek,

4

我知道这是一个老问题,但是,我认为提供另一个解决方案可能会有所帮助。

等效于AngularJS

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

在Angular 2+中,您不能在同一元素上使用* ngFor和* ngIf,因此它将如下:

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

如果不能用作内部容器,请使用ng-container代替。如果要在应用程序中有条件地附加一组元素(即使用* ngIf =“ foo”),但又不想用其他元素包装它们,则ng-container很有用。


4

根据这里和其他地方的答案,我创建了一个小矮人。

此外,我不得不添加@Input@ViewChildElementRef<input>创建和subscribe()以可观察到它。

Angular2搜索过滤器:PLUNKR(更新:插件不再起作用)


3

管道将是最好的方法。但低于一个也可以。

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

这可能会破坏某些东西。例如在垫子形式的区域内
pcnate

2

这是我的代码:

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);
    }
}

样品:

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

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

1

我喜欢用于特定于应用程序的筛选器的另一种方法是在组件上使用自定义的只读属性,与使用自定义管道(IMHO)相比,该属性使您可以更清晰地封装筛选逻辑。

例如,如果我想绑定albumList并过滤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;
}

要绑定到HTML中,您可以然后绑定到只读属性:

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

我发现对于特定于应用程序的专用过滤器,它比管道更有效,因为它保持与组件过滤器相关的逻辑。

管道对于全局可重用的过滤器效果更好。


1
此方法不会触发连续的脏检查,而不是使用valueChanged方法吗?
列昂佩尔蒂埃

1

我创建了以下管道,用于从列表中获取所需的项目。

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);
  }

}

小写转换只是以不区分大小写的方式进行匹配。您可以像这样在您的视图中使用它:

<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>

1

理想情况下,您应该为此创建angualr 2管道。但是您可以做到这一点。

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

1

基于上面提出的非常优雅的回调管道解决方案,可以通过允许传递其他过滤器参数来对其进一步概括。然后我们有:

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));
  }
}

零件

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>

0

这是我创建一段时间并在博客上发布的示例,其中包含一个正在工作的插件。它提供了可以过滤任何对象列表的过滤器管道。基本上,您只需在ngFor规范中指定属性和值{key:value}。

它与@NateMay的响应没有太大不同,只是我以相对冗长的细节进行了解释。

就我而言,我使用这种标记过滤了用户输入的一些文本(filterText)的无序列表,该文本是针对我数组中对象的“ label”属性的:

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

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


0

您使用创建过滤器的第一步 @Pipe在component.ts文件中:

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
    }
})

和Person对象的数据结构:

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

在您的html文件视图中:

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>

0

这是你的数组

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

        }
    ];

这是您的ngFor循环Filter By:

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

在那里,我使用产品的filterProduct Instant,因为我想保留原始数据。这里的_filterText模型用作输入框,任何时候都会调用setter函数。在setFilterText中,performProduct被调用,它将仅返回与输入匹配的结果。我使用小写字母,不区分大小写。

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);
        }

0

经过一番谷歌搜索,我遇到了ng2-search-filter。In将获取您的对象并将搜索词应用于所有对象属性以寻找匹配项。


0

我正在寻找使通过对象的过滤器的方法,然后可以像多重过滤器一样使用它: 多重过滤器示例

我做了这个美容解决方案:

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">...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.