在这种情况下,我正在使用 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;
}
}
7 回答
为了完全理解问题和可能的解决方案,我们需要讨论角度变化检测 - 用于管道和组件 .
换管检测
无状态/纯管
默认情况下,管道是无状态/纯粹的 . 无状态/纯管道只是将输入数据转换为输出数据 . 他们没有任何属性 - 只是一个
transform()
方法 . 因此,Angular可以优化无状态/纯管道的处理:如果他们的输入需要在变化检测周期中执行 . 对于{{power | exponentialStrength: factor}}
,power
和factor
等管道,输入 .对于这个问题,
"#student of students | sortByName:queryElem.value"
,students
和queryElem.value
是输入,而管道sortByName
是无状态/纯 .students
是一个数组(引用) .添加学生时,数组引用不会更改 -
students
不会更改 - 因此不会执行无状态/纯管道 .当在过滤器输入中输入内容时,
queryElem.value
确实发生了变化,因此执行了无状态/纯管道 .解决阵列问题的一种方法是每次添加学生时更改数组引用 - 即,每次添加学生时创建新数组 . 我们可以用
concat()
做到这一点:虽然这有效,但我们的
addNewStudent()
方法不应该使用管道 . 我们想使用push()
添加到我们的数组中 .有状态管道
有状态管道具有状态 - 它们通常具有属性,而不仅仅是
transform()
方法 . 即使输入没有改变,也可能需要对它们进行评估 . 当我们指定管道是有状态/非纯粹的时候 - 那么每当Angular的变更检测系统检查组件的变化并且该组件使用有状态管道时,它将检查管道的输出,无论其输入是否已经改变 .这听起来像我们想要的,即使效率较低,因为即使
students
引用没有改变,我们也希望管道执行 . 如果我们只是使管道有状态,我们会收到一个错误:根据@drewmoore's answer,“此错误仅在开发模式下发生(默认情况下从beta-0开始) . 如果在引导应用程序时调用
enableProdMode()
,则不会抛出错误 . ” docs for ApplicationRef.tick()州:在我们的场景中,我认为错误是虚假/误导 . 我们有一个有状态的管道,每次调用时输出都会改变 - 它可能有副作用,这没关系 . NgFor在管道之后进行评估,因此它应该可以正常工作 .
但是,我们无法真正开发此错误,因此一种解决方法是将数组属性(即状态)添加到管道实现并始终返回该数组 . 请参阅@ pixelbits对此解决方案的回答 .
但是,我们可以更高效,并且正如我们将看到的,我们将不需要管道实现中的数组属性,并且我们不需要针对双重更改检测的变通方法 .
组件更改检测
默认情况下,在每个浏览器事件中,角度变化检测都会遍历每个组件以查看它是否发生了变化 - 输入和模板(以及其他东西?)都会被检查 .
如果我们知道组件仅依赖于其输入属性(和模板事件),并且输入属性是不可变的,那么我们可以使用更有效的
onPush
更改检测策略 . 使用此策略,不会检查每个浏览器事件,只有在输入更改和模板事件触发时才会检查组件 . 而且,显然,我们没有使用此设置得到Expression ... has changed after it was checked
错误 . 这是因为不再检查onPush
组件直到它再次"marked"(ChangeDetectorRef.markForCheck()
) . 因此,模板绑定和有状态管道输出仅执行/评估一次 . 除非输入改变,否则无状态/纯管道仍未执行 . 所以我们仍然需要一个有状态的管道 .这是@EricMartinez建议的解决方案:带有
onPush
变化检测的有状态管道 . 请参阅@ caffinatedmonkey对此解决方案的回答 .请注意,使用此解决方案,
transform()
方法不需要每次都返回相同的数组 . 我发现有点奇怪:没有状态的有状态管道 . 再考虑一下......有状态管道可能应该总是返回相同的数组 . 否则,它只能在开发模式下与onPush
组件一起使用 .毕竟,我认为我喜欢@Eric _2607189的答案的组合:有状态管道返回相同的数组引用,如果组件允许,则返回
onPush
更改检测 . 由于有状态管道返回相同的数组引用,因此管道仍可用于未使用onPush
配置的组件 .Plunker
这可能会成为Angular 2的习惯用法:如果数组正在输入管道,并且数组可能会更改(数组中的项目,而不是数组引用),我们需要使用有状态管道 .
正如Eric Martinez在评论中指出的那样,将
pure: false
添加到您的Pipe
装饰者和changeDetection: ChangeDetectionStrategy.OnPush
添加到您的Component
装饰器将解决您的问题 . Here is a working plunkr.更改为ChangeDetectionStrategy.Always
,也有效 . 这就是原因 .According to the angular2 guide on pipes:
至于ChangeDetectionStrategy,默认情况下,每个循环都会检查所有绑定 . 添加
pure: false
管道时,我认为由于性能原因,更改检测方法将从CheckAlways
更改为CheckOnce
. 对于OnPush
,只有在输入属性更改或触发事件时才会检查Component的绑定 . 有关变化探测器的更多信息,angular2
的重要部分,请查看以下链接:Change Detection in Angular 2
https://github.com/angular/angular/issues/4746
Change Detection Reinvented Victor Savkin
Demo Plunkr
您无需更改ChangeDetectionStrategy . 实现有状态管道足以让一切正常运行 .
这是一个有状态的管道(没有进行其他更改):
来自angular documentation
Pure and impure pipes
有两类管道:纯净和不纯 . 管道默认是纯净的 . 到目前为止,你见过的每根烟斗都是纯净的 . 通过将其纯标志设置为false来使管道不纯 . 你可以让FlyingHeroesPipe不纯洁:
@Pipe({ name: 'flyingHeroesImpure', pure: false })
在此之前,了解纯净和不纯的区别,从纯管道开始 .
纯管道Angular仅在检测到输入值的纯变化时才执行纯管道 . 纯变化是对原始输入值(String,Number,Boolean,Symbol)的更改或更改的对象引用(Date,Array,Function,Object) .
Angular忽略(复合)对象内的更改 . 如果更改输入月份,添加到输入数组或更新输入对象属性,它将不会调用纯管道 .
这似乎有限制但它也很快 . 对象引用检查比深度检查差异要快得多 - 因此Angular可以快速确定它是否可以跳过管道执行和视图更新 .
因此,当您可以使用变化检测策略时,最好使用纯管道 . 如果不能,可以使用不纯的管道 .
解决方法:在构造函数中手动导入Pipe并使用此管道调用transform方法
实际上你甚至不需要管道
而不是做纯:假 . 您可以通过this.students = Object.assign([],NEW_ARRAY)深度复制和替换组件中的值;其中NEW_ARRAY是修改后的数组 .
它适用于角度6,也适用于其他角度版本 .
添加管道额外参数,并在数组更改后立即更改,即使使用纯管道,列表也会刷新
让项目项目|管:PARAM