了解为什么 Angular 不附带过滤和排序管道以及如何创建自定义过滤管道
Angular 为最常见的场景提供了几个内置管道。
最大的缺席之一是过滤管。 Angular 不提供内置过滤器管道是有充分理由的。
为什么 Angular 不附带过滤管
Angular 中没有用于过滤和排序列表的管道,尽管 AngularJS 曾经提供 filter 和 orderBy。
这不是一个错误。
因为这样的管道性能很差并且禁止激进的缩小,Angular 并没有开箱即用地提供它们。 过滤成本很高。
尽管计算能力每年都在增长,但数据和信息也在不断增长!
一般来说,过滤需要与对象属性对应的参数。 然而,有些场景要求管道是不纯的,这意味着 Angular 几乎在每次更改检测周期发生时都会调用不纯管道。
因此,过滤和排序成为代价高昂的操作,尤其是在数据量很大的情况下。
当 Angular 每秒多次调用这些管道时,即使是中等大小的列表,用户体验也可能会受到很大影响。
如何创建纯 FilterPipe
首先,让我们看一下模板和类。
应用组件和数据
在模板中,我们有一个输入元素,它使用双向绑定将用户输入存储在一个名为 filterBy 的属性中。 请记住在 AppModule 中导入 FormsModule 以在您的应用程序中使用双向绑定。
下面,我们使用 NgFor 从 usersList 中列出一些用户的名字。 这是代码:
<div> <label for="filter">Filter by: </label> <input type="text" id="filter" [(ngModel)]="filterBy" /></div><div *ngFor="let user of usersList> <ul> <li>{{ user.name }}</li> </ul></div>
import { Component, VERSION } from '@angular/core';import { User } from './model';@Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'],})export class AppComponent { name = 'Angular ' + VERSION.major; filterBy; usersList: User[] = [...] // omitted for clarity}
用户列表 usersList 是一个包含十个项目的数组,例如 users。 它存储在类中,您可以在 jsonplaceholder 或此应用的 StackBlitz 中找到它。
每个用户都是一个具有多个属性的对象。 对于我们的目的,知道其中一个属性是名称就足够了。
我们想按名称过滤用户。
初始应用程序如下所示:
创建 FilterPipe 类
让我们从使用 Angular CLI 命令 ng generate pipe filter 开始,其中 filter 是将在模板绑定中使用的管道名称。
作为旁注,您还可以使用 ng g p 管道。
Angular 将负责创建文件、填充一些字段并将其正确导入 app.module.ts。
import { Pipe, PipeTransform } from '@angular/core';@Pipe({ name: 'filter'})export class FilterPipe implements PipeTransform { transform(value: any, args?: any): any { return null; }}
在 transform 方法中,value 是模板中管道左侧的值,而 args? 是我们将用于过滤值的可选参数。
要使用此过滤器,我们只需将其添加到模板中,如下所示:
<div *ngFor="let user of usersList | filter">
自然,什么都不会发生。但是,请注意 usersList 是通过管道的属性,并且与 transform 方法中的 value 参数相关联。
将过滤代码添加到 FilterPipe
我们现在将在 FilterPipe 中的 transform 方法中工作。
变换方法的参数
首先,我们使用以下代码更新 transform 方法的参数:
transform(value: User[], filterString: string, property: string)
我们在上面讨论了价值。值的类型是形状像用户界面的对象数组。您可以在 StackBlitz 的 model.ts 中看到这一点。
第二个参数是 string 类型的 filterString。 filterString 的值必须来自模板。因此,我们将向管道添加一个参数。参数将是 filterBy ,即用户输入,例如,用户想要用作过滤器的字符串。
第三个也是最后一个论点是财产。由于我们的用户对象包含多个属性,因此我们需要指定要使用的属性。为了简单起见,我将字符串名称硬编码为模板中过滤器管道的第二个参数。
这是一个例子。通常,您不应该在代码中硬编码值。
在模板中,管道变为:
...<div *ngFor="let user of usersList | filter: filterBy:'name'"> <ul> <li>{{ user.name }}</li> </ul></div>
其中 filter 是管道的名称,filterBy 是我们从输入元素中获得的属性,这要归功于双向绑定,而 'name' 是用户对象中的硬编码属性。
您需要的参数数量是可选的,根据您的需要。
transform方法中的过滤逻辑
现在我们在 transform 方法中工作。
我们首先添加一些代码以在没有过滤器时返回每个值。
if (value.length === 0 || !filterString) { return value;}
然后,我们添加逻辑以根据用户的字符串进行过滤。
let filteredUsers: User[] = [];for (let user of value) { if (user[property].includes(filterString)) { filteredUsers.push(user); }}return filteredUsers;
请记住,属性是硬编码的,它意味着名称。 value 是 usersList 中的用户列表,filterString 是一个人想要用作过滤器的字符串,例如 filterBy。
因此,您可以将这段代码读作“对于userList中的每个用户,如果用户名包含filterBystring,则将此用户添加到filteredUsersarray”最终返回。
到目前为止,这是 FilterPipe:
import { Pipe, PipeTransform } from '@angular/core';import { User } from './model';@Pipe({ name: 'filter',})export class FilterPipe implements PipeTransform { transform(value: User[], filterString: string, property: string): User[] { if (value.length === 0 || !filterString) { return value; } let filteredUsers: User[] = []; for (let user of value) { if (user[property].toLowerCase().includes(filterString.toLowerCase())) { filteredUsers.push(user); } } return filteredUsers; }}
此时,管道允许我们很好地过滤名称。
请注意,我添加了 Javascript toLowerCase() 方法,以便过滤器不区分大小写。 当然,这完全是可选的。
那么昂贵的过滤操作呢?
如何使 FilterPipe 不纯
让我们更进一步。
我们添加一个简单的按钮,使用以下代码将新用户添加到 usersList:
<button (click)="onAddUser()">Add user</button>
该方法如下所示:
onAddUser() { this.usersList.push({ id: Math.floor(Math.random() * 10000), name: 'Leanne Graham', username: 'Bret', ... });}
每次单击时,都会将一个用户推送到 usersList。
这就是问题所在!
如果您过滤 Leanne,然后单击该按钮,则用户将添加到 usersList,但不会呈现过滤后的列表。因此,您不会在过滤列表中看到新用户,即使您在删除过滤器后会立即看到它们。
每次复合数组或对象更改时,Angular 都不会在数据上重新运行管道。
纯净和不纯净的管道
更准确地说,我们需要谈谈纯管道和非纯管道。
当输入值发生纯变化时,我们有一个纯管道。纯粹的更改可以是以下之一:
- 对原始输入值(字符串、数字、布尔值、符号)的更改
- 对对象引用(日期、数组、函数、对象)的更改
纯管道使用纯函数。给定相同的输入,纯函数应该总是返回相同的输出。
默认情况下,管道是纯的,因为 Angular 忽略了对象或数组内部的变化。因此,当我们添加到输入数组时,它不会调用纯管道,就像我们的例子一样,或者更新输入对象属性。
您可以猜到,对象引用检查比深度检查差异更快。此外,当 Angular 跳过管道执行时,它也会跳过视图更新。
因此,当常规变化检测策略很好时,最好使用纯管道。
当我们需要进行深度检查时,例如在当前示例中,我们需要使用不纯管道。
在每个组件变化检测周期中都会有一条不纯的管道运行。例如,它可以在每次击键或鼠标移动时运行。
你可以猜到,这很快就变得非常昂贵。
话虽如此,实现不纯管道就像在管道装饰器中声明 pure: false 一样简单。
@Pipe({ name: 'filter', pure: false,})
通过将 pure 属性添加到 Pipe 装饰器,我们强制 FilterPipe 在每次数据更改时更新。
在管道内添加一个日志以可视化频率。
使用不纯管道可能会导致性能和激进的最小化问题。 因此,请谨慎使用,仅在真正需要时使用。
在 StackBlitz 上查找代码或从 GitHub 克隆应用程序。
异步管道
作为旁注,值得一提的是异步管道是不纯的。 粗略地说,我们可以争辩说“管道总是在检查新的输入数据时是不纯的”。
虽然 Angular 异步管道优化了更改检测,但它也消除了手动使用订阅和取消订阅的必要性。
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:dandanxi6@qq.com