NgFor不与Angular2管更新数据 [英] NgFor doesn't update data with Pipe in Angular2

查看:2196
本文介绍了NgFor不与Angular2管更新数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这种情况下,我的学生展示(阵列)的列表与ngFor的观点:

 <李* ngFor =学生#学生> {{student.name}}< /李>

这是美妙的,它更新每当我添加其他学生名单。

然而,当我给它一个管道由学生名称过滤,

 <李* ngFor =学生#学生| sortByName:queryElem.value> {{student.name}}< /李>

直到我键入过滤学生姓名领域的东西它不更新列表。

下面是一个链接到 plnkr

Hello_world.html

 < H1>学生:LT; / H1>
<标签=newStudentName>< /标签>
<输入类型=文本名称=newStudentName占位符=newStudentName#newStudentElem>
<按钮(点击)=addNewStudent(newStudentElem.value)>添加新的学生< /按钮>
< BR>
<输入类型=文本占位符=搜索#queryElem(KEYUP)=0>
< UL>
  <李* ngFor =学生#学生| sortByName:queryElem.value> {{student.name}}< /李>
< / UL>

sort_by_name_pipe.ts

 进口{}管道从'angular2 /核心;@管({
  名称:'sortByName
})
出口类SortByNamePipe {  变换(值,[的queryString]){
    //执行console.log(价值的queryString);
    返回value.filter((学生)=>新建的RegExp(的queryString).TEST(student.name))
    //返回值;
  }
}


解决方案

要充分认识这个问题,可能的解决方案,我们需要讨论角变化检测 - 管道和部件

管道变化检测

无国籍/纯管道

在默认情况下,管道是无状态的/纯。无国籍/纯水管改造只是将数据输入输出数据。他们什么都不记得了,所以他们没有任何属性&ndash的;只是一个变换()方法。因此角可以优化治疗无国籍/纯水管的:如果它们的输入不会改变,管道不需要变更检测周期中被执行。对于管道,如 {{电源| exponentialStrength:因素}} 功率系数是输入

有关这个问题,#学生的学生| sortByName:queryElem.value学生 queryElem.value 是输入和管道 sortByName 是无状态的/纯。 学生是一个数组(参考)。


  • 当增加一个学生,数组的参考的不会改变&ndash的; 学生不改变&ndash的;因此不执行无国籍/纯管。

  • 当事情被输入到滤波器输入, queryElem.value 是变化的,因此,在执行无国籍/纯管道。

要解决这个问题,数组的一个方法是每一个学生加入&ndash的时间来改变数组引用;即创建一个新的数组每次添加一个学生的时间。我们可以用 CONCAT做到这一点()

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

虽然这个工程,我们的 addNewStudent()方法不应该得到执行,只是因为我们使用的管道一定的方式。我们要使用推()添加到我们的数组。

状态管道

状态管道有状态 - 他们通常有属性,而不仅仅是一个变换()方法。他们可能需要即使它们的输入没有改变进行评估。当我们指定一个管状态/非纯&ndash的; 纯:假&ndash的;然后每当角的变化检测系统检查一个组件更改和该组件使用有状态管,它将检查管道的输出,其输入是否已经改变或不

这听起来像我们所希望的,即使它是低效率的,因为我们希望即使学生引用未改变管道来执行。如果我们简单地使管道有状态的,我们得到一个错误:

 例外:EX pression的学生| sortByName:queryElem.value中的HelloWorld @ 7:6'
它被检查之后已经改变。 previous值:'[对象的对象],[对象的对象]。
当前值:'[对象的对象],[对象的对象]在[学生| sortByName:queryElem.value

根据 @ drewmoore的回答,这种错误仅发生在开发模式(这是默认启用的测试版-0)。如果你打电话 enableProdMode()自举程序的时候,误差不会抛出。该文档为 ApplicationRef.tick() 状态:


  

在开发模式下,勾选()也执行第二次变更检测周期,以确保检测没有进一步的变化。如果有其他变化本次周期回升,在应用程序绑定有副作用,不能在一个单一的变化探测通得到解决。在这种情况下,角抛出一个错误,因为一个角度应用程序只能有一个变化检测通期间,所有的变化检测必须完成。


在我们的场景,我相信错误是假的/误导。我们有一个状态管,并在每次被称为ndash的时间的输出可以改变;它可以有副作用,这就是好的。管道后NgFor进行评估,因此它应该工作正常。

但是,我们不能真的被抛出这个错误发展,因此一个解决方法是一个数组属性(即状态)添加到管道实现,总是返回数组。见@ pixelbits的回答此解决方案。

不过,我们可以更加高效,正如我们所看到的,我们将不再需要在管道实现数组属性,我们将不再需要一个解决办法的双重变化检测。

组件变化检测

默认情况下,每一个浏览器的事件,角变化检测经过每一个部件,看它是否改变&ndash的;输入和模板(也许其他的东西?)被选中。

如果我们知道一个组件只取决于它的输入属性(和模板的事件),以及输入性是不可改变的,我们可以用更有效的 onPush 变化检测策略。有了这个战略,而不是在每一个浏览器事件检查,一个组件只有当输入改变,当模板事件会触发检查。而且,很显然,我们没有得到防爆pression它检查错误使用此设置后......发生了变化。这是因为 onPush 组件没有再次检查,直到它被标注( ChangeDetectorRef.markForCheck())再次。所以模板绑定和状态输出管道执行/只计算一次。无国籍/纯管道仍然没有执行,除非它们的输入改变。因此,我们仍然需要一个有状态的管道在这里。

这是解决方案@EricMartinez建议:状态烟斗 onPush 变化检测。见@ caffinatedmonkey的回答此解决方案。

请注意与此解决方案的变换()方法并不需要每次都返回相同的阵列。我觉得有点奇怪,但:有状态的管道没有状态。关于它的思考多一些......有状态的管道可能总是返回相同的阵列。在开发模式,否则它只能与 onPush 中使用的组件。


所以,在这一切之后,我觉得我喜欢的@ Eric的和@ pixelbits的答案的组合:有状态的管道将返回相同的数组引用,以 onPush 变化检测,如果组件允许它。因为有状态管道返回相同的数组引用,管道仍​​然可以不与 onPush 配置组件使用。

<大骨节病> Plunker

这可能会成为一个角2成语:如果一个数组是喂养管,数组可能,我们需要使用一个有状态的管道(即不是数组引用数组中的项目)变更

In this scenario, I'm displaying a list of students (array) to the view with ngFor:

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

It's wonderful that it updates whenever I add other student to the list.

However, when I give it a pipe to filter by the student name,

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

it does not update the list until I type something in the filtering student name field.

Here's a link to 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;
  }
}

解决方案

To fully understand the problem and possible solutions, we need to discuss Angular change detection -- for pipes and components.

Pipe Change Detection

Stateless/pure Pipes

By default, pipes are stateless/pure. Stateless/pure pipes simply transform input data into output data. They don't remember anything, so they don't have any properties – just a transform() method. Angular can therefore optimize treatment of stateless/pure pipes: if their inputs don't change, the pipes don't need to be executed during a change detection cycle. For a pipe such as {{power | exponentialStrength: factor}}, power and factor are inputs.

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).

  • When a student is added, the array reference doesn't change – students doesn't change – hence the stateless/pure pipe is not executed.
  • When something is typed into the filter input, queryElem.value does change, hence the stateless/pure pipe is executed.

One way to fix the array issue is to change the array reference each time a student is added – i.e., create a new array each time a student is added. We could do this with concat():

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

Although this works, our addNewStudent() method shouldn't have to be implemented a certain way just because we're using a pipe. We want to use push() to add to our array.

Stateful Pipes

Stateful pipes have state -- they normally have properties, not just a transform() method. They may need to be evaluated even if their inputs haven't changed. When we specify that a pipe is stateful/non-pure – pure: false – then whenever Angular's change detection system checks a component for changes and that component uses a stateful pipe, it will check the output of the pipe, whether its input has changed or not.

This sounds like what we want, even though it is less efficient, since we want the pipe to execute even if the students reference hasn't changed. If we simply make the pipe stateful, we get an error:

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's answer, "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.

In our scenario I believe the error is bogus/misleading. We have a stateful pipe, and the output can change each time it is called – it can have side-effects and that's okay. NgFor is evaluated after the pipe, so it should work fine.

However, we can't really develop with this error being thrown, so one workaround is to add an array property (i.e., state) to the pipe implementation and always return that array. See @pixelbits's answer for this solution.

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.

Component Change Detection

By default, on every browser event, Angular change detection goes through every component to see if it changed – inputs and templates (and maybe other stuff?) are checked.

If we know that a component only depends on its input properties (and template events), and that the input properties are immutable, we can use the much more efficient onPush change detection strategy. With this strategy, instead of checking on every browser event, a component is checked only when the inputs change and when template events trigger. And, apparently, we don't get that Expression ... has changed after it was checked error with this setting. This is because an onPush component is not checked again until it is "marked" (ChangeDetectorRef.markForCheck()) again. So Template bindings and stateful pipe outputs are executed/evaluated only once. Stateless/pure pipes are still not executed unless their inputs change. So we still need a stateful pipe here.

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

Note that with this solution the transform() method doesn't need to return the same array each time. I find that a bit odd though: a stateful pipe with no state. Thinking about it some more... the stateful pipe probably should always return the same array. Otherwise it could only be used with onPush components in dev mode.


So after all that, I think I like a combination of @Eric's and @pixelbits's answers: stateful pipe that returns the same array reference, with onPush change detection if the component allows it. Since the stateful pipe returns the same array reference, the pipe can still be used with components that are not configured with onPush.

Plunker

This will probably become an Angular 2 idiom: if an array is feeding a pipe, and the array might change (the items in the array that is, not the array reference), we need to use a stateful pipe.

这篇关于NgFor不与Angular2管更新数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆