NgFor 不使用 Angular2 中的管道更新数据 [英] NgFor doesn't update data with Pipe in Angular2

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

问题描述

在这种情况下,我使用 ngFor 向视图显示学生列表(数组):

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.

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

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.

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

<小时>

推荐答案

为了充分理解问题和可能的解决方案,我们需要讨论 Angular 变化检测——针对管道和组件.

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

默认情况下,管道是无状态/纯的.无状态/纯管道只是将输入数据转换为输出数据.他们不记得任何东西,所以他们没有任何属性——只是一个 transform() 方法.因此,Angular 可以优化对无状态/纯管道的处理:如果它们的输入没有改变,则不需要在变更检测周期中执行这些管道.对于诸如 {{power | 之类的管道指数强度:因子}}powerfactor 是输入.

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.

对于这个问题,"#student of student |sortByName:queryElem.value"studentsqueryElem.value 是输入,管道 sortByName 是无状态/纯的.students 是一个数组(参考).

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 确实会发生变化,因此会执行无状态/纯管道.
  • 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.

解决数组问题的一种方法是每次添加学生时更改数组引用 - 即,每次添加学生时创建一个新数组.我们可以用 concat() 做到这一点:

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

虽然这有效,但我们的 addNewStudent() 方法不应该仅仅因为我们使用管道就必须以某种方式实现.我们想使用 push() 添加到我们的数组中.

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.

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

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.

这听起来像我们想要的,尽管效率较低,因为我们希望即使 students 引用没有改变,管道也能执行.如果我们只是让管道有状态,我们会得到一个错误:

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

根据@drewmoore 的回答,此错误仅在开发模式下发生(从β-0).如果您在引导应用程序时调用 enableProdMode(),则不会抛出错误."ApplicationRef.tick() 的文档 状态:

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:

在开发模式下,tick() 还会执行第二个更改检测周期,以确保不会检测到进一步的更改.如果在第二个周期中进行了其他更改,则应用程序中的绑定会产生无法在单个更改检测过程中解决的副作用.在这种情况下,Angular 会抛出一个错误,因为一个 Angular 应用程序只能有一个变更检测阶段,在此期间所有的变更检测都必须完成.

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 在管道之后被评估,所以它应该可以正常工作.

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.

但是,我们无法真正开发出抛出此错误的方法,因此一种解决方法是向管道实现添加一个数组属性(即状态)并始终返回该数组.请参阅 @pixelbits 对此解决方案的回答.

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.

默认情况下,在每个浏览器事件中,Angular 更改检测都会遍历每个组件以查看它是否发生了更改——检查输入和模板(可能还有其他内容?).

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.

如果我们知道一个组件只依赖于它的输入属性(和模板事件),并且输入属性是不可变的,我们就可以使用更高效的onPush变化检测策略.使用这种策略,不是检查每个浏览器事件,而是仅在输入更改和模板事件触发时检查组件.而且,显然,我们没有得到 Expression ... 在检查后发生变化 错误与此设置.这是因为 onPush 组件在它被标记"之前不会被再次检查.(ChangeDetectorRef.markForCheck()) 再次.所以模板绑定和有状态管道输出只执行/评估一次.除非输入更改,否则仍不会执行无状态/纯管道.所以我们这里仍然需要一个有状态的管道.

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.

这是@EricMartinez 建议的解决方案:带有 onPush 更改检测的有状态管道.请参阅@caffinatedmonkey 对此解决方案的回答.

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

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

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.

所以毕竟,我想我喜欢@Eric 和@pixelbits 的答案的组合:返回相同数组引用的有状态管道,如果组件允许,则带有 onPush 更改检测.由于有状态管道返回相同的数组引用,管道仍可用于未使用 onPush 配置的组件.

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

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

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天全站免登陆