为了充分理解问题和可能的解决方案,我们需要讨论Angular 变化检测--针对管道和组件.
管道变化检测
无状态/纯管道
默认情况下,管道是无状态/纯状态的.无状态/纯管道只是将输入数据转换为输出数据.他们什么都不记得,所以他们没有任何属性——只有一个transform()
方法.因此,Angular可以优化对无状态/纯管道的处理:如果它们的输入没有更改,那么在更改检测周期中就不需要执行管道.对于管道,例如{{power | exponentialStrength: factor}}
、power
和factor
是输入.
对于这个问题,"#student of students | sortByName:queryElem.value"
、students
和queryElem.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习惯用法:如果一个数组供给管道,并且该数组可能会更改(数组中的项,即不是数组引用),我们需要使用有状态管道.