在这种情况下,我使用ngFor
向视图中显示学生列表(数组):
<li *ngFor="#student of students">{{student.name}}</li>
每当我将其他学生添加到列表中时,它都会更新。
但是,当我按学生名称给pipe
filter
时,
<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;
}
}
答案 0 :(得分:134)
为了完全理解问题和可能的解决方案,我们需要讨论角度变化检测 - 用于管道和组件。
默认情况下,管道是无状态/纯粹的。无状态/纯管道只是将输入数据转换为输出数据。他们不记得任何东西,所以他们没有任何属性 - 只是一种transform()
方法。因此,Angular可以优化对无状态/纯管道的处理:如果它们的输入没有改变,则在变化检测周期期间不需要执行管道。对于{{power | exponentialStrength: factor}}
之类的管道,power
和factor
是输入。
对于此问题,"#student of students | sortByName:queryElem.value"
,students
和queryElem.value
是输入,而管道sortByName
是无状态/纯粹的。 students
是一个数组(引用)。
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
根据@drewmoore's answer,&#34;此错误仅发生在开发模式(默认情况下从beta-0启用)。如果您在引导应用时调用enableProdMode()
,则错误不会被抛出。&#34; docs for ApplicationRef.tick()
州:
在开发模式下,tick()还执行第二个更改检测周期,以确保不会检测到进一步的更改。如果在第二个周期中拾取了其他更改,则应用中的绑定会产生无法在单个更改检测过程中解决的副作用。在这种情况下,Angular会抛出一个错误,因为Angular应用程序只能有一个更改检测通道,在此期间必须完成所有更改检测。
在我们的场景中,我认为错误是虚假/误导。我们有一个有状态的管道,每次调用时输出都会改变 - 它可能有副作用,这没关系。在管道之后评估NgFor,所以它应该可以正常工作。
但是,我们无法在抛出此错误的情况下进行开发,因此一种解决方法是将数组属性(即状态)添加到管道实现并始终返回该数组。请参阅@ pixelbits对此解决方案的回答。
但是,我们可以提高效率,而且正如我们所看到的,我们在管道实现中不需要数组属性,并且我们不需要双变化检测的解决方法
默认情况下,在每个浏览器事件中,Angular变化检测会遍历每个组件以查看它是否发生了变化 - 输入和模板(以及其他东西?)都会被检查。
如果我们知道组件仅依赖于其输入属性(和模板事件),并且输入属性是不可变的,那么我们可以使用更有效的onPush
更改检测策略。使用此策略,不会检查每个浏览器事件,只有在输入更改和模板事件触发时才会检查组件。而且,显然,我们在此设置下不会出现Expression ... has changed after it was checked
错误。这是因为onPush
组件在被标记为&#34;之后不再被检查。 (ChangeDetectorRef.markForCheck()
)再次。因此,模板绑定和有状态管道输出仅执行/评估一次。除非输入改变,否则无状态/纯管道仍未执行。所以我们仍然需要一个有状态的管道。
这是@EricMartinez建议的解决方案:具有onPush
更改检测的有状态管道。请参阅@ caffinatedmonkey对此解决方案的回答。
请注意,使用此解决方案时,transform()
方法不需要每次都返回相同的数组。我发现有点奇怪:没有状态的有状态管道。再考虑一下......有状态管道可能应该总是返回相同的数组。否则,它只能在开发模式下与onPush
组件一起使用。
毕竟,我认为我喜欢@Eric&#39;和@ pixelbits的答案的组合:有状态管道返回相同的数组引用,如果组件有onPush
更改检测允许它。由于有状态管道返回相同的数组引用,因此管道仍然可以与未使用onPush
配置的组件一起使用。
这可能会成为Angular 2的习惯用法:如果数组正在输入管道,并且数组可能会更改(数组中的项目,而不是数组引用),我们需要使用有状态管道。
答案 1 :(得分:27)
正如Eric Martinez在评论中指出的那样,将pure: false
添加到Pipe
装饰者和changeDetection: ChangeDetectionStrategy.OnPush
添加到Component
装饰者将解决您的问题。 Here is a working plunkr.更改为ChangeDetectionStrategy.Always
,也有效。这就是原因。
According to the angular2 guide on pipes:
管道默认为无状态。我们必须通过将
pure
装饰器的@Pipe
属性设置为false
来声明管道是有状态的。此设置告诉Angular的变化检测系统在每个循环中检查该管道的输出,无论其输入是否已经改变。
对于ChangeDetectionStrategy
,默认情况下,每个循环都会检查所有绑定。添加pure: false
管道后,我认为由于性能原因,更改检测方法会从CheckAlways
更改为CheckOnce
。使用OnPush
时,仅在输入属性更改或触发事件时才会检查Component的绑定。有关变更检测器的更多信息,angular2
的重要部分,请查看以下链接:
答案 2 :(得分:19)
您无需更改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;
}
}
答案 3 :(得分:9)
纯净且不纯的管道
有两类管道:纯净和不纯净。管道默认是纯净的。到目前为止,你见过的每根烟斗都是纯净的。通过将其纯标志设置为false来使管道不纯。你可以让FlyingHeroesPipe不纯洁:
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
在此之前,要了解纯净和不纯的区别,从纯管道开始。
纯净的管道 Angular仅在检测到输入值的纯更改时才执行纯管道。纯变化是对原始输入值(String,Number,Boolean,Symbol)的更改或更改的对象引用(Date,Array,Function,Object)。
Angular忽略(复合)对象内的更改。如果更改输入月份,添加到输入数组或更新输入对象属性,它将不会调用纯管道。
这看起来似乎有限制,但也很快。对象引用检查比深度检查差异要快得多 - 因此Angular可以快速确定它是否可以跳过管道执行和视图更新。
因此,当您可以使用变化检测策略时,最好使用纯管道。如果不能,可以使用不纯的管道。
答案 4 :(得分:0)
一种解决方法:在构造函数中手动导入Pipe并使用此管道调用转换方法
constructor(
private searchFilter : TableFilterPipe) { }
onChange() {
this.data = this.searchFilter.transform(this.sourceData, this.searchText)}
实际上,您甚至不需要管道
答案 5 :(得分:0)
不是执行pure:false。您可以使用this.students = Object.assign([],NEW_ARRAY);复制并替换组件中的值。其中NEW_ARRAY是修改后的数组。
它适用于角度6,也应该适用于其他角度版本。
答案 6 :(得分:0)
添加到管道的额外参数,并在数组更改后立即更改它,即使使用纯管道,列表也会刷新
让项目项| pipe:参数
答案 7 :(得分:0)
在这种情况下,我使用ts文件中的Pipe进行数据过滤。与使用纯管道相比,它在性能上要好得多。在这样的ts中使用:
import { YourPipeComponentName } from 'YourPipeComponentPath';
class YourService {
constructor(private pipe: YourPipeComponentName) {}
YourFunction(value) {
this.pipe.transform(value, 'pipeFilter');
}
}
答案 8 :(得分:0)
创建不纯管道的性能代价很高。 因此不要创建不纯管道,而是在数据更改时通过创建数据副本来更改数据变量的引用,并在原始数据变量中重新分配副本的引用。
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();
}