Angular 2筛选/搜索列表

64

我正在寻找Angular 2的方法来完成这个

我有一个项目列表,我想创建一个输入框来筛选该列表。

<md-input placeholder="Item name..." [(ngModel)]="name"></md-input>

<div *ngFor="let item of items">
{{item.name}}
</div>

在Angular 2中,实际的做法是什么?需要使用管道吗?


1
你可以为此创建自定义管道,这将是最佳选择。 - Vinay Pandya
1
你能举个例子吗? - TheUnreal
你可以使用这个,看一下“HeroSearchComponent”标题,或者使用这个作为参考点。 - ulubeyn
这是一个很好的例子,但在我的情况下,我没有使用HTTP,我只需要在接收到项目列表后在客户端中进行过滤,而不需要任何其他响应。 - TheUnreal
12个回答

110

按多个字段搜索

数据假设:

items = [
  {
    id: 1,
    text: 'First item'
  },
  {
    id: 2,
    text: 'Second item'
  },
  {
    id: 3,
    text: 'Third item'
  }
];

标记语言:

<input [(ngModel)]="query">
<div *ngFor="let item of items | search:'id,text':query">{{item.text}}</div>

管道:

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

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {
  public transform(value, keys: string, term: string) {

    if (!term) return value;
    return (value || []).filter(item => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));

  }
}

万事一行!


很棒的答案,伙计 - 我在想你是否知道如何将[(ngModel)]="query"附加到单选按钮上?类似这样? <input [(ngModel)]="query" type="radio" name="topic.term" [value]="topic.term"/>{{topic.term}} - Towelie
你需要使用'change'事件,像这样:`<div *ngFor="let item of items"> <input type="radio" name="{{item.text}}" [checked]="query == item.text" (change)="query = item.text" [value]="item.text"/>{{item.text}}</div>` - Moshe Quantz
我在这里创建了一个更具体的问题 https://stackoverflow.com/questions/44877635/angular2-filter-pipe-using-radio-buttons-to-filter/ - Towelie
10
这个解决方案可行!并且适用于小型列表!但总体而言,Angular团队有意忽略这种类型的管道:https://angular.io/guide/pipes#no-filter-pipe - austin_ce
1
@MosheQuantz 你能告诉我哪种方法更有效吗?通过管道还是在ngModel查询值更改时过滤数组的普通函数?为什么? - Sukanya Pai
显示剩余6条评论

64

您需要通过在input事件上保持侦听器,根据每次输入更改手动过滤结果。在进行手动筛选时,请确保您应该维护两个变量副本,一个是原始集合副本,另一个是filteredCollection 副本。这种方式的优点是可以节省几次不必要的过滤操作,从而提高性能。可能会有更多的代码,但这将更加性能友好。

标记 - HTML 模板

<md-input #myInput placeholder="Item name..." [(ngModel)]="name" (input)="filterItem(myInput.value)"></md-input>

<div *ngFor="let item of filteredItems">
   {{item.name}}
</div>

代码

assignCopy(){
   this.filteredItems = Object.assign([], this.items);
}
filterItem(value){
   if(!value){
       this.assignCopy();
   } // when nothing has typed
   this.filteredItems = Object.assign([], this.items).filter(
      item => item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
   )
}
this.assignCopy();//when you fetch collection from server.

这是一个不错的方法,但并没有按预期工作:只有在输入失焦时列表才会更改,实际上它删除了我输入名称的项目,而不是显示名称与输入相同的项目。 - TheUnreal
没问题,我只是把 item.name... 改成了 !item.name,现在可以搜索了。唯一的问题是,如果我输入的是“BOX”,而有一个名为“BLACK BOX”的物品,它将不会显示出来,因为“BOX”是第二个单词(它只从第一个单词开始搜索,而不是所有单词)。你有什么想法吗? - TheUnreal
2
这个答案有一点修改,不要使用 item.property 只过滤一个属性,可以使用 JSON.stringify(item) 并使用其中的所有属性。 - Zahema
我正在尝试同样的事情,但它不起作用。@TheUnreal,请问你能告诉我确切的答案吗?我也想通过多个值进行过滤,而不仅仅是通过名称。 - ananya

15

HTML

<input [(ngModel)] = "searchTerm" (ngModelChange) = "search()"/>
<div *ngFor = "let item of items">{{item.name}}</div>

组件

search(): void {
    let term = this.searchTerm;
    this.items = this.itemsCopy.filter(function(tag) {
        return tag.name.indexOf(term) >= 0;
    }); 
}

请注意,this.itemsCopy 等于 this.items,在进行搜索之前应该先设置它。


1
什么是标签? - Muhammed Moussa
@MuhammedMoussa "tag" 是该函数的回调变量,在这种情况下是 item 对象。请参阅 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter - MaxRocket
1
我喜欢这个回复,因为它没有使用管道,而是使用了Javascript过滤器。 - MaxRocket

5
在Angular 2中,我们没有预定义的过滤器和排序功能,就像在AngularJs中一样,我们需要根据自己的需求创建它。虽然这需要花费时间,但我们必须这样做(请参见无FilterPipe或OrderByPipe)。在本文中,我们将看到如何在Angular 2中创建名为pipe的过滤器和称为OrderBy的排序功能。让我们使用一个简单的虚拟JSON数据数组来进行演示。以下是我们将用于示例的JSON数据。
首先,我们将看到如何通过使用搜索功能来使用管道(过滤器):
创建一个名为category.component.ts的组件。

    import { Component, OnInit } from '@angular/core';
    @Component({
      selector: 'app-category',
      templateUrl: './category.component.html'
    })
    export class CategoryComponent implements OnInit {

      records: Array<any>;
      isDesc: boolean = false;
      column: string = 'CategoryName';
      constructor() { }

      ngOnInit() {
        this.records= [
          { CategoryID: 1,  CategoryName: "Beverages", Description: "Coffees, teas" },
          { CategoryID: 2,  CategoryName: "Condiments", Description: "Sweet and savory sauces" },
          { CategoryID: 3,  CategoryName: "Confections", Description: "Desserts and candies" },
          { CategoryID: 4,  CategoryName: "Cheeses",  Description: "Smetana, Quark and Cheddar Cheese" },
          { CategoryID: 5,  CategoryName: "Grains/Cereals", Description: "Breads, crackers, pasta, and cereal" },
          { CategoryID: 6,  CategoryName: "Beverages", Description: "Beers, and ales" },
          { CategoryID: 7,  CategoryName: "Condiments", Description: "Selishes, spreads, and seasonings" },
          { CategoryID: 8,  CategoryName: "Confections", Description: "Sweet breads" },
          { CategoryID: 9,  CategoryName: "Cheeses",  Description: "Cheese Burger" },
          { CategoryID: 10, CategoryName: "Grains/Cereals", Description: "Breads, crackers, pasta, and cereal" }
         ];
         // this.sort(this.column);
      }
    }
<div class="col-md-12">
  <table class="table table-responsive table-hover">
    <tr>
      <th >Category ID</th>
      <th>Category</th>
      <th>Description</th>
    </tr>
    <tr *ngFor="let item of records">
      <td>{{item.CategoryID}}</td>
      <td>{{item.CategoryName}}</td>
      <td>{{item.Description}}</td>
    </tr>
  </table>
</div>

2.这段代码没有什么特别的,只是用一个包含类别的列表初始化了我们的记录变量,同时还声明了另外两个变量isDesc和column用于后面的排序。最后添加了this.sort(this.column)。后续我们将使用这个方法。

注意templateUrl: './category.component.html',下一步我们将创建一个名为category.component.html的HTML页面,以表格形式显示记录。

为此,请创建一个名为category.component.html的HTML页面,并添加以下代码:

3.在这里,我们使用ngFor来重复记录并逐行显示,尝试运行它,我们可以看到所有记录都在表格中。

搜索-筛选记录

假设我们想按类别名称搜索表格,为此让我们添加一个文本框来输入并搜索。

<div class="form-group">
  <div class="col-md-6" >
    <input type="text" [(ngModel)]="searchText" 
           class="form-control" placeholder="Search By Category" />
  </div>
</div>

5.现在我们需要创建一个管道来按类别搜索结果,因为过滤器不再像angularjs中那样可用。

创建一个名为category.pipe.ts的文件,并将以下代码添加到其中。

    import { Pipe, PipeTransform } from '@angular/core';
    @Pipe({ name: 'category' })
    export class CategoryPipe implements PipeTransform {
      transform(categories: any, searchText: any): any {
        if(searchText == null) return categories;

        return categories.filter(function(category){
          return category.CategoryName.toLowerCase().indexOf(searchText.toLowerCase()) > -1;
        })
      }
    }

6. 在transform方法中,我们接受类别列表和搜索文本来搜索/过滤列表上的记录。将此文件导入到我们的category.component.ts文件中,我们希望在此处使用它,如下所示:

import { CategoryPipe } from './category.pipe';
@Component({      
  selector: 'app-category',
  templateUrl: './category.component.html',
  pipes: [CategoryPipe]   // This Line       
})

7.我们的ngFor循环现在需要使用管道来过滤记录,因此请将其更改为以下内容。您可以在下面的图像中查看输出。

在此输入图像描述


代码可以运行,但是你错过了调用筛选器的一个步骤 <tr *ngFor="let item of records | search : searchText"> 请将其添加到 .html 文件中。你能提供另一种多级筛选的解决方案吗? - Prasanna

5

数据

names = ['Prashobh','Abraham','Anil','Sam','Natasha','Marry','Zian','karan']

你可以通过创建一个简单的管道来实现这一点。
<input type="text" [(ngModel)]="queryString" id="search" placeholder="Search to type">

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

@Pipe({
    name: 'FilterPipe',
})
export class FilterPipe implements PipeTransform {
    transform(value: any, input: string) {
        if (input) {
            input = input.toLowerCase();
            return value.filter(function (el: any) {
                return el.toLowerCase().indexOf(input) > -1;
            })
        }
        return value;
    }
}

这将根据搜索词过滤结果。

更多信息


你好,能否告诉我如何使用这个数组:[{"id":"1","name":"aa"},{"id":"2","name":"bb"}...]。我在 https://stackoverflow.com/questions/44180587/angular2-auto-suggester 上提出了一个问题,如果您能回答将会很有帮助。先谢谢了。 - Narendra Vyas
请相应更改此行代码:return el.toLowerCase().indexOf(input) > -1; 在您的情况下,应该是el.name.toLowerCase().indexOf(input) > -1; - Prashobh

2

您还可以创建一个搜索管道来过滤结果:

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

@Pipe({
  name : 'searchPipe',
})
export class SearchPipe implements PipeTransform {
  public transform(value, key: string, term: string) {
    return value.filter((item) => {
      if (item.hasOwnProperty(key)) {
        if (term) {
          let regExp = new RegExp('\\b' + term, 'gi');
          return regExp.test(item[key]);
        } else {
          return true;
        }
      } else {
        return false;
      }
    });
  }
}

在HTML中使用管道:

<md-input placeholder="Item name..." [(ngModel)]="search" ></md-input>
<div *ngFor="let item of items | searchPipe:'name':search ">
  {{item.name}}
</div>

输入任何搜索名称都不会显示列表中的任何项目。 - TheUnreal

1

Angular 2+中的管道是一种很好的方式,可以直接从模板中转换和格式化数据。

管道允许我们在模板内部更改数据;例如过滤、排序、格式化日期、数字、货币等。一个快速的例子是通过在模板代码中应用简单的过滤器将字符串转换为小写。

内置管道列表来自API列表示例

{{ user.name | uppercase }}

Angular 4.4.7版本的示例。 ng version

自定义管道,可以接受多个参数。

HTML « *ngFor="let student of students | jsonFilterBy:[searchText, 'name'] "
TS   « transform(json: any[], args: any[]) : any[] { ... }

使用管道过滤内容 « 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;
  }
}

JsonFilterByPipe添加到模块的声明列表中;如果忘记这样做,将会出现jsonFilterBy提供程序错误。 如果添加到模块,则可用于该模块中的所有组件。

@NgModule({
  imports: [
    CommonModule,
    RouterModule,
    FormsModule, ReactiveFormsModule,
  ],
  providers: [ StudentDetailsService ],
  declarations: [
    UsersComponent, UserComponent,

    JsonFilterByPipe,
  ],
  exports : [UsersComponent, UserComponent]
})
export class UsersModule {
    // ...
}

文件名:users.component.tsStudentDetailsService 是从这个链接创建的。

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

}

文件名: 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>

1
谢谢。我尝试了上面的几个例子,这个是对我有效的。 - Pete Herc

1
尝试这个
<md-input #myInput placeholder="Item name..." [(ngModel)]="name"></md-input>

<div *ngFor="let item of filteredItems | search: name">
   {{item.name}}
</div>

使用搜索管道
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'search'
})
export class SearchPipe implements PipeTransform {

  transform(value: any, args?: any): any {

    if(!value)return null;
    if(!args)return value;

    args = args.toLowerCase();

    return value.filter(function(item){
        return JSON.stringify(item).toLowerCase().includes(args);
    });
}

}

0

这段代码对我来说几乎可以工作...但是我想要一个多元素过滤器,所以我对过滤管道进行了修改:

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

@Pipe({ name: 'jsonFilterBy' })
@Injectable()
export class JsonFilterByPipe implements PipeTransform {

  transform(json: any[], args: any[]): any[] {
    const searchText = args[0];
    const jsonKey = args[1];
    let jsonKeyArray = [];

    if (searchText == null || searchText === 'undefined') { return json; }

    if (jsonKey.indexOf(',') > 0) {
        jsonKey.split(',').forEach( function(key) {
            jsonKeyArray.push(key.trim());
        });
    } else {
        jsonKeyArray.push(jsonKey.trim());
    }

    if (jsonKeyArray.length === 0) { return json; }

    // Start with new Array and push found objects onto it.
    let returnObjects = [];
    json.forEach( function ( filterObjectEntry ) {

        jsonKeyArray.forEach( function (jsonKeyValue) {
            if ( typeof filterObjectEntry[jsonKeyValue] !== 'undefined' &&
            filterObjectEntry[jsonKeyValue].toLowerCase().indexOf(searchText.toLowerCase()) > -1 ) {
                // object value contains the user provided text.
                returnObjects.push(filterObjectEntry);
                }
            });

    });
    return returnObjects;
  }
} 

现在,不再是

jsonFilterBy:[ searchText, 'name']

你可以做到

jsonFilterBy:[ searchText, 'name, other, other2...']

0
稍微修改一下@Mosche的答案,以处理如果不存在过滤器元素的情况。
TS:
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'filterFromList'
})
export class FilterPipe implements PipeTransform {
    public transform(value, keys: string, term: string) {

        if (!term) {
            return value
        }
        let res = (value || []).filter((item) => keys.split(',').some(key => item.hasOwnProperty(key) && new RegExp(term, 'gi').test(item[key])));
        return res.length ? res : [-1];

    }
}

现在,在您的HTML中,您可以通过“-1”值检查是否没有结果。 HTML:
<div *ngFor="let item of list | filterFromList: 'attribute': inputVariableModel">
            <mat-list-item *ngIf="item !== -1">
                <h4 mat-line class="inline-block">
                 {{item}}
                </h4>
            </mat-list-item>
            <mat-list-item *ngIf="item === -1">
                No Matches
            </mat-list-item>
        </div>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接