NgFor不会使用Angular2中的Pipe更新数据

在这种情况下,我使用ngFor显示了学生(数组)的视图列表:

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

当我将其他学生添加到列表中时,它会更新。

但是,当我给它一个pipe道过滤学生的名字,

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

要充分了解问题和可能的解决scheme,我们需要讨论angular度变化检测 – 对于pipe道和组件。

pipe道变化检测

无状态/纯pipe道

默认情况下,pipe道是无状态/纯的。 无状态/纯pipe道将input数据转换为输出数据。 他们不记得任何东西,所以他们没有任何属性 – 只是一个transform()方法。 因此,angular度可以优化无状态/纯pipe道的处理:如果其input不变,则在变化检测周期中不需要执行pipe道。 对于诸如{{power | exponentialStrength: factor}} {{power | exponentialStrength: factor}}powerfactor是input。

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

  • 当添加学生时,数组引用不会改变 – students不会改变 – 因此无状态/纯pipe道不会执行。
  • 当input的内容被input时, queryElem.value会改变,因此执行无状态/纯pipe道。

解决数组问题的一种方法是在每次添加学生时更改数组引用 – 即每次添加学生时创build一个新数组。 我们可以用concat()来做到这一点:

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

尽pipe这样做有效,但我们的addNewStudent()方法不应该因为使用pipe道而以某种方式实现。 我们要使用push()添加到我们的数组。

有状态的pipe道

有状态的pipe道具有状态 – 它们通常具有属性,而不仅仅是一个transform()方法。 即使他们的投入没有改变,他们可能也需要被评估。 当我们指定一个pipe道是有状态的/非纯的 – pure: false – 那么无论何时Angular的变化检测系统检查一个组件的变化,并且这个组件使用有状态的pipe道,它将检查pipe道的输出,input是否改变或不。

这听起来像我们想要的,尽pipe它效率较低,因为即使students参考没有改变,我们也希望pipe道执行。 如果我们简单地使pipe状态,我们得到一个错误:

 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的回答 ,“这个错误只发生在dev模式下(默认情况下启用beta-0),如果在启动应用程序时调用enableProdMode() ,错误不会被抛出。 ApplicationRef.tick()状态的文档 :

在开发模式中,tick()还会执行第二个更改检测周期,以确保不会检测到进一步的更改。 如果在第二个周期中发现其他更改,则应用程序中的绑定具有无法在单个更改检测阶段中解决的副作用。 在这种情况下,Angular将引发错误,因为Angular应用程序只能进行一次更改检测,必须完成所有更改检测。

在我们的情况下,我认为这个错误是虚假的/误导性的。 我们有一个有状态的pipe道,每次调用它的输出都可以改变 – 它可能有副作用,这没关系。 NgFor是在pipe道后面评估的,所以应该可以正常工作。

但是,我们不能真正开发这个错误被抛出,所以一个解决方法是将一个数组属性(即状态)添加到pipe道实现,并始终返回该数组。 请参阅@ pixelbits对此解决scheme的回答。

但是,我们可以更高效,正如我们将看到的,我们将不需要pipe道实现中的数组属性,我们也不需要双重变化检测的解决方法。

组件更改检测

默认情况下,在每个浏览器事件中,angular度更改检测会遍历每个组件,以查看是否更改了input和模板(以及其他东西?)。

如果我们知道一个组件只依赖于它的input属性(和模板事件),并且input属性是不可变的,我们可以使用更有效的onPush变化检测策略。 使用这种策略,不是检查每个浏览器事件,只有在input发生变化以及模板事件触发时才检查组件。 而且,显然,我们没有得到这个Expression ... has changed after it was checked错误Expression ... has changed after it was checked了这个设置。 这是因为onPush组件在再次被“标记”( ChangeDetectorRef.markForCheck() )之前不会被再次检查。 所以模板绑定和有状态的pipe道输出只执行/评估一次。 无状态/纯pipe道仍然不执行,除非它们的input发生变化。 所以我们在这里仍然需要一个有状态的pipe道。

这是@EricMartinezbuild议的解决scheme:带有onPush变化检测的状态pipe道。 请参阅@ caffinatedmonkey对此解决scheme的回答。

请注意,使用此解决scheme, transform()方法不需要每次都返回相同的数组。 我觉得有点奇怪:一个没有状态的状态pipe道。 再想一想……有状态的pipe道应该总是返回相同的数组。 否则,它只能在开发模式下与onPush组件一起使用。


所以毕竟,我想我喜欢@ Eric和@pixelbits的答案的组合:有状态的pipe道,返回相同的数组引用, onPush变化检测,如果组件允许它。 由于有状态pipe道返回相同的数组引用,所以pipe道仍然可以与未configurationonPush组件一起onPush

Plunker

这可能会成为一个Angular 2成语:如果一个数组正在馈送一个pipe道,并且该数组可能会改变(数组中的项目,而不是数组引用),我们需要使用有状态的pipe道。

正如Eric Martinez在评论中指出的那样,在您的Pipe装饰器中添加pure: false ,并且将changeDetection: ChangeDetectionStrategy.OnPush到您的Component装饰器中将会解决您的问题。 这是一个有用的plunkr。 更改为ChangeDetectionStrategy.Always ,也可以。 这是为什么。

根据pipe道上的angular度2指导 :

pipe道默认是无状态的。 我们必须通过将@Pipe装饰器的pure性设置为false来声明一个pipe道是有状态的。 这个设置告诉Angular的变化检测系统在每个周期检查这个pipe道的输出,不pipe它的input是否改变。

至于ChangeDetectionStrategy ,默认情况下,每个周期检查所有的绑定。 当添加一个pure: falsepipe道时,出于性能方面的原因,我认为更改检测方法从CheckAlways更改为CheckAlways 。 使用OnPush ,只有在input属性发生更改或触发事件时,才会检查组件绑定。 有关更改检测器( angular2的重要组成部分)的更多信息,请查看以下链接:

演示Plunkr

你不需要改变ChangeDetectionStrategy。 实现一个有状态的Pipe足以让所有的工作。

这是一个有状态的pipe道(没有其他更改):

 @Pipe({ name: 'sortByName', pure: false }) export class SortByNamePipe { tmp = []; transform (value, [queryString]) { this.tmp.length = 0; // console.log(value, queryString); var arr = value.filter((student)=>new RegExp(queryString).test(student.name)); for (var i =0; i < arr.length; ++i) { this.tmp.push(arr[i]); } return this.tmp; } } 

从angular度文件

纯净和不纯的pipe道

有两类pipe道:纯粹的和不纯的。 pipe道默认是纯的。 到目前为止,你看到的每个pipe道都是纯净的。 通过将纯旗设置为false,可以使pipe道不纯。 你可以让FlyingHeroesPipe不纯,像这样:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

在这之前,先了解纯净和不纯的区别,从纯净的pipe道开始。

纯pipe道Angular只在检测到纯input值的变化时执行纯pipe道。 纯粹的改变是改变原始input值(string,数字,布尔,符号)或改变的对象引用(date,数组,函数,对象)。

Angular忽略(复合)对象内的变化。 如果您更改input月份,添加到input数组或更新input对象属性,它将不会调用纯pipe道。

这可能看起来很有限,但速度也很快。 对象引用检查速度比深入检查差异快得多,所以Angular可以快速确定是否可以跳过pipe道执行和视图更新。

出于这个原因,当你能够接受变化检测策略时,一个纯粹的pipe道是最好的select。 当你不能,你可以使用不纯的pipe道。