NgFor 不使用 Angular2中的管道更新数据

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

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

当我把其他学生添加到列表中时,它就会更新,这真是太棒了。

然而,当我给它一个 pipefilter的学生名字,

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

直到我在筛选学生名字字段中键入某些内容之后,它才会更新列表。

这是 普林克的链接。

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

64169 次浏览

正如 Eric Martinez 在评论中指出的,将 pure: false添加到 Pipe装饰器,将 changeDetection: ChangeDetectionStrategy.OnPush添加到 Component装饰器将解决您的问题。改成 ChangeDetectionStrategy.Always也可以。这就是原因。

According to the angular2 guide on pipes:

默认情况下,管道是无状态的。我们必须通过将 @Pipe装饰符的 pure属性设置为 false来将管道声明为有状态的。这个设置告诉 Angular 的变化检测系统每个循环都要检查这个管道的输出,无论输入是否改变。

对于 ChangeDetectionStrategy,默认情况下,每个周期都会检查所有绑定。当增加一个 pure: false管道时,我相信由于性能的原因,变化检测方法会从 CheckAlways变为 CheckOnce。使用 OnPush,只有当输入属性更改或触发事件时才检查组件的绑定。有关 angular2的重要组成部分变更检测器的更多信息,请查看以下链接:

演示普朗克

您不需要更改 ChangeDetectionStrategy。

这是一个有状态管道(没有进行其他更改) :

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

为了充分理解这个问题和可能的解决方案,我们需要讨论管道和部件的角度变化检测。

变化检测

Stateless/pure Pipes

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

For this question, "#student of students | sortByName:queryElem.value", students and queryElem.value are inputs, and pipe sortByName is stateless/pure. students is an array (reference).

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

修复数组问题的一种方法是在每次添加学生时更改数组引用——也就是说,在每次添加学生时创建一个新数组。我们可以用 concat():

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

尽管这样可以工作,但是我们的 addNewStudent()方法不应该仅仅因为使用管道就必须以某种特定的方式实现。我们希望使用 push()添加到数组中。

有状态管道

有状态管道具有状态——它们通常具有属性,而不仅仅是 transform()方法。即使它们的输入没有改变,也可能需要对它们进行评估。当我们指定一个管道是有状态/非纯粹的-pure: false-那么每当 Angular 的变化检测系统检查一个组件是否发生变化,而该组件使用有状态管道时,它就会检查管道的输出,无论输入是否发生变化。

这听起来像是我们想要的,即使效率较低,因为我们希望管道执行,即使 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

According to @ Drewmoore 的回答, "this error only happens in dev mode (which is enabled by default as of beta-0). If you call enableProdMode() when bootstrapping the app, the error won't get thrown." The docs for ApplicationRef.tick() state:

In development mode, tick() also performs a second change detection cycle to ensure that no further changes are detected. If additional changes are picked up during this second cycle, bindings in the app have side-effects that cannot be resolved in a single change detection pass. In this case, Angular throws an error, since an Angular application can only have one change detection pass during which all change detection must complete.

在我们的场景中,我认为这个错误是伪造的/误导性的。我们有一个有状态管道,每次调用它时,输出都会发生变化——它可能会产生副作用,这没什么。在管道之后计算 NgFor,因此它应该可以正常工作。

但是,我们不能在抛出这个错误的情况下进行开发,因此一个解决方案是向管道实现添加一个数组属性(即状态) ,并始终返回该数组。请参见@pixelbits 对这个解决方案的回答。

However, we can be more efficient, and as we'll see, we won't need the array property in the pipe implementation, and we won't need a workaround for the double change detection.

组件变化检测

默认情况下,在每个浏览器事件中,角度变化检测都会检查每个组件,看它是否发生了变化——输入和模板(或者其他东西?)都检查过了。

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

This is the solution @EricMartinez suggested: stateful pipe with onPush change detection. See @caffinatedmonkey's answer for this solution.

注意,使用这个解决方案时,transform()方法不需要每次返回相同的数组。但我发现这有点奇怪: 一个没有状态的有状态管道。进一步考虑一下... ... 有状态管道可能应该总是返回相同的数组。否则,它只能在 dev 模式下与 onPush组件一起使用。


综上所述,我认为我喜欢这两个答案的组合: 返回相同数组引用的有状态管道,如果组件允许的话,还有 onPush变化检测。因为有状态管道返回相同的数组引用,所以管道仍然可以与未配置 onPush的组件一起使用。

Plunker

这可能会成为一个 Angular 2习惯用法: 如果一个数组正在输入一个管道,并且该数组可能会发生变化(数组中的项,而不是数组引用) ,那么我们需要使用一个有状态管道。

来自 angular documentation

纯净和不纯净的管道

有两种类型的管道: 纯的和不纯的。默认情况下,管道是纯的。到目前为止,你看到的每根烟斗都是纯净的。通过将管道的纯旗设置为 false,可以使管道变得不纯。你可以把“飞翔的英雄”弄得像这样不纯洁:

@ 烟斗({ 姓名: “飞翔的英雄不纯洁” 纯: 假 })

在这样做之前,先从纯管道开始,了解纯管道和不纯管道的区别。

Pure pipes Angular 只有在检测到输入值的纯更改时才执行纯管道。纯更改是对基本输入值(字符串、数字、布尔值、符号)或已更改的对象引用(日期、数组、函数、对象)的更改。

角度忽略(复合)对象内部的变化。如果您更改输入月份、添加到输入数组或更新输入对象属性,它不会调用纯管道。

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

出于这个原因,当你能够接受变化检测策略时,纯管道更可取。当你不能的时候,你可以使用不纯净的管道。

解决方案: 在构造函数中手动导入管道,并使用此管道调用转换方法

constructor(
private searchFilter : TableFilterPipe) { }


onChange() {
this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

其实你根本不需要烟斗

而不是做纯粹的: 错误的。您可以深度复制和替换组件中的值,方法是 this.Students = Object.sign ([] ,NEW _ ARRAY) ; 其中 NEW _ ARRAY 是修改后的数组。

它适用于角度6,也应该适用于其他角度的版本。

添加到管道额外的参数,并在数组更改后立即更改它,即使使用纯管道,列表也将刷新

let item of items | pipe:param

In this usage case i used my Pipe in ts file for data filtering. It is much better for performance, than using pure pipes. Use in ts like this:

import { YourPipeComponentName } from 'YourPipeComponentPath';


class YourService {


constructor(private pipe: YourPipeComponentName) {}


YourFunction(value) {
this.pipe.transform(value, 'pipeFilter');
}
}

产生不纯净的管道在性能上是昂贵的。 所以不要创建不纯粹的管道,而是在数据变化时通过创建数据拷贝来改变数据变量的引用,并在原始数据变量中重新分配拷贝的引用。

            emp=[];
empid:number;
name:string;
city:string;
salary:number;
gender:string;
dob:string;
experience:number;


add(){
const temp=[...this.emps];
const e={empid:this.empid,name:this.name,gender:this.gender,city:this.city,salary:this.salary,dob:this.dob,experience:this.experience};
temp.push(e);
this.emps =temp;
//this.reset();
}

现在没有必要把事情复杂化!

在新版本的 Angular 中,使管道变得不纯净不会导致开发模式中的任何错误。我猜想当前接受的答案中提到的错误与 这个问题有关,它被解析为5.5(!)几年前(这个问题发布后不久)。

据我所知,Angular 现在使用 IterableDiffer来检测由不纯管道返回的数组的变化,就像它检测出现在模板 (when the default change detection strategy is used),中的普通数组一样,所以当数组引用发生变化时,如果它的内容没有发生变化,它就不会认为这是一个问题。这意味着如果我们每次都生成一个新的数组,就不会有任何错误,所以我们只需要让管道变得不纯粹,这样就可以解决问题了。

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


@Pipe({
name: 'sortByName',
pure: false
})
export class SortByNamePipe implements PipeTransform {
transform(value, queryString) {
return value.filter(student => new RegExp(queryString).test(student.name));
}
}