在这个场景中,我将向视图显示一个学生列表(数组),其中包含ngFor个:

<li *ngFor="#student of students">{{student.name}}</li>

每当我将其他学生添加到名单中时,它都会更新,这真是太棒了.

然而,当我以学生的名字给它打pipefilter分时,

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

在我在筛选学生姓名字段中键入内容之前,它不会更新列表.

这是plnkr的链接.

Hello_world.html

<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
    <li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>

sort_by_name_pipe.ts

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'sortByName'
})
export class SortByNamePipe {

    transform(value, [queryString]) {
        // console.log(value, queryString);
        return value.filter((student) => new RegExp(queryString).test(student.name))
        // return value;
    }
}

推荐答案

为了充分理解问题和可能的解决方案,我们需要讨论Angular 变化检测--针对管道和组件.

管道变化检测

无状态/纯管道

默认情况下,管道是无状态/纯状态的.无状态/纯管道只是将输入数据转换为输出数据.他们什么都不记得,所以他们没有任何属性——只有一个transform()方法.因此,Angular可以优化对无状态/纯管道的处理:如果它们的输入没有更改,那么在更改检测周期中就不需要执行管道.对于管道,例如{{power | exponentialStrength: factor}}powerfactor是输入.

对于这个问题,"#student of students | sortByName:queryElem.value"studentsqueryElem.value是输入,而管道sortByName是无状态/纯的.students是一个数组(参考).

  • 添加学生时,数组reference不会更改-students不会更改-因此不会执行无状态/纯管道.
  • 当在过滤输入中键入一些内容时,queryElem.value确实会更改,因此执行无状态/纯管道.

解决数组问题的一种方法是每次添加学生时更改数组引用,即每次添加学生时创建一个新array.我们可以用concat():

this.students = this.students.concat([{name: studentName}]);

尽管这是可行的,但我们的addNewStudent()方法不应该仅仅因为使用管道就必须以某种方式实现.我们想用push()来增加我们的array.

有状态的管道

有状态管道有状态--它们通常有属性,而不仅仅是transform()方法.即使他们的投入没有改变,也可能需要对其进行判断.当我们将管道指定为有状态/非纯管道(pure: false)时,只要ANGLE的更改检测系统判断组件的更改,并且该组件使用有状态管道,它就会判断管道的输出,无论其输入是否已更改.

这听起来像是我们想要的,尽管效率较低,因为我们希望即使students引用没有更改,管道也能执行.如果我们只是让管道有状态,我们会得到一个错误:

EXCEPTION: Expression 'students | sortByName:queryElem.value  in HelloWorld@7:6' 
has changed after it was checked. Previous value: '[object Object],[object Object]'. 
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value

根据@drewmoore's answer的说法,"这个错误只发生在开发模式下(从beta-0开始默认启用).如果你在 bootstrap 应用程序时调用enableProdMode(),错误不会被抛出."docs for ApplicationRef.tick()国:

在开发模式下,tick()还执行第二个更改检测周期,以确保不会检测到进一步的更改.如果在第二个周期中采用了额外的更改,则应用程序中的绑定具有不能在单个更改检测过程中解决的副作用.在这种情况下,ANGLING会抛出错误,因为ANGLE应用程序只能有一个更改检测遍,在此期间所有更改检测都必须完成.

在我们的场景中,我认为错误是假的/误导性的.我们有一个有状态管道,每次调用它时输出都会改变-它可能有副作用,这没什么.ngFor在管道之后求值,因此它应该可以正常工作.

但是,我们不能在抛出此错误的情况下进行真正的开发,因此一种解决方法是向管道实现添加数组属性(即,state),并始终返回该array.有关此解决方案,请参阅@Pixelbit的答案.

但是,我们可以提高效率,正如我们将看到的那样,我们不需要管道实现中的数组属性,也不需要用于双重更改检测的变通方法.

元件变化检测

默认情况下,在每个浏览器事件上,Angular 变化检测都会遍历每个零部件,以查看它是否发生了变化-输入和模板(可能还有其他内容?)都被判断过了.

如果我们知道一个组件只依赖于它的输入属性(和模板事件),并且输入属性是不可变的,那么我们可以使用效率更高的onPush更改检测策略.使用此策略时,不是判断每个浏览器事件,而是仅在输入更改和模板事件触发时判断组件.显然,我们在此设置中没有得到Expression ... has changed after it was checked错误.这是因为直到它再次被"标记"(ChangeDetectorRef.markForCheck()),才再次判断onPush分量.因此,模板绑定和有状态管道输出只执行/判断一次.无状态/纯管道仍然不会执行,除非它们的输入更改.所以我们在这里仍然需要一个有状态的管道.

这就是@EricMartinez建议的解决方案:有状态管道,具有onPush个变化检测.有关此解决方案,请参见@caffinatedmonkey的答案.

请注意,使用此解决方案,transform()方法不需要每次返回相同的array.但我觉得这有点奇怪:一个有状态的管道没有状态.再想想...有状态管道可能应该始终返回相同的array.否则,它只能在开发模式下与onPush个组件一起使用.


因此,在这之后,我想我喜欢@Eric和@Pixelbits的答案的组合:返回相同数组引用的有状态管道,如果组件允许的话,还可以检测onPush个更改.由于有状态管道返回相同的数组引用,因此该管道仍然可以与未配置为onPush的组件一起使用.

Plunker

这可能会变成一个Angular 2习惯用法:如果一个数组供给管道,并且该数组可能会更改(数组中的项,即不是数组引用),我们需要使用有状态管道.

Angular相关问答推荐

在Angular中将路由解析器提供程序和组件提供程序相结合

Angular 17@在大小写时切换多个值

为什么在回调完成之前,可观察对象会返回?

在Angular中使用ngFor进行表乘法

如何将Angular 服务包含在延迟加载的独立组件路由中?

如何将角17中的嵌套组件与独立组件一起使用?

显示用户名-Angular

ngx-http-client 在 Angular 16 中已弃用

自定义垫日历中选定的日期(Angular material )

Moment.js 和 Angular 库

个人修改 Angular CLI - 创建新应用

将数组传递给 URLSearchParams,同时使用 http 调用获取请求

如何重新加载当前页面?

如何在数据表angular material中显示空消息,如果未找到数据

如何使用 Bootstrap 4 flexbox 填充可用内容?

Angular:ViewChild 上未定义 nativeElement

BrowserAnimationsModule 和 NoopAnimationsModule 有什么区别?

Angular 2:将外部js文件导入组件

如何将 @Input 与使用 ComponentFactoryResolver 创建的组件一起使用?

Angular2 - 如何从应用程序外部调用组件功能