124

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

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

每当我将其他学生添加到列表中时,它都会更新,这真是太好了。

但是,当我pipefilter学生的名字命名时,

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

4

10 回答 10

176

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

管道变化检测

无状态/纯管道

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

对于这个问题,"#student of students | sortByName:queryElem.value"studentsqueryElem.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 的回答,“此错误仅在开发模式下发生(从 beta-0 开始默认启用)。如果您enableProdMode()在引导应用程序时调用,则不会引发错误。” 状态的文档ApplicationRef.tick()

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

在我们的场景中,我认为该错误是虚假/误导性的。我们有一个有状态的管道,每次调用时输出都会改变——它可能有副作用,这没关系。NgFor 在管道之后进行评估,因此它应该可以正常工作。

但是,我们不能在抛出这个错误的情况下进行真正的开发,因此一种解决方法是向管道实现添加一个数组属性(即状态)并始终返回该数组。有关此解决方案,请参阅@pixelbits 的答案。

但是,我们可以更有效率,正如我们将看到的,在管道实现中我们不需要数组属性,也不需要双重变化检测的解决方法。

组件变化检测

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

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

这是@EricMartinez 建议的解决方案:带onPush变更检测的有状态管道。有关此解决方案,请参阅@caffinatedmonkey 的答案。

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


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

Plunker

这可能会成为 Angular 2 的习惯用法:如果数组正在输入管道,并且数组可能会改变(数组中的项目,即数组引用,而不是数组引用),我们需要使用有状态管道。

于 2015-12-28T16:56:59.640 回答
30

正如 Eric Martinez 在评论中指出的那样,添加pure: false到您的Pipe装饰器和changeDetection: ChangeDetectionStrategy.OnPush您的Component装饰器将解决您的问题。这是一个工作plunkr。更改为ChangeDetectionStrategy.Always, 也可以。这就是为什么。

根据管道上的 angular2 指南

管道默认是无状态的。我们必须通过将装饰器的pure属性设置为 来声明管道是有状态的。这个设置告诉 Angular 的变化检测系统在每个周期检查这个管道的输出,不管它的输入是否发生了变化。@Pipefalse

至于ChangeDetectionStrategy,默认情况下,每个周期都会检查所有绑定。添加管道时pure: false,我相信更改检测方法会出于性能原因更改为 from CheckAlwaysto 。CheckOnce使用OnPush,仅在输入属性更改或触发事件时检查组件的绑定。有关更改检测器的更多信息,这是 的重要组成部分angular2,请查看以下链接:

于 2015-12-25T02:30:46.150 回答
24

演示 Plunkr

您无需更改 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;
  }
}
于 2015-12-25T22:46:19.047 回答
21

角度文档

纯净和不纯净的管道

有两类管道:纯的和不纯的。管道默认是纯的。到目前为止,您看到的每根管道都是纯净的。您可以通过将其 pure 标志设置为 false 来使管道不纯。您可以像这样使 FlyingHeroesPipe 不纯:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

在此之前,先从纯管道开始,了解纯和不纯的区别。

纯管道 Angular 仅在检测到输入值发生纯更改时才会执行纯管道。纯更改是对原始输入值(字符串、数字、布尔值、符号)的更改或更改的对象引用(日期、数组、函数、对象)。

Angular 会忽略(复合)对象内的变化。如果您更改输入月份、添加到输入数组或更新输入对象属性,它不会调用纯管道。

这可能看起来有限制,但它也很快。对象引用检查速度很快——比深度检查差异快得多——因此 Angular 可以快速确定它是否可以跳过管道执行和视图更新。

出于这个原因,当您可以使用更改检测策略时,最好使用纯管道。当你不能时,你可以使用不纯的管道。

于 2017-06-06T12:13:59.147 回答
2

而不是做纯:假。您可以通过 this.students = Object.assign([], NEW_ARRAY); 深拷贝和替换组件中的值 其中 NEW_ARRAY 是修改后的数组。

它适用于 Angular 6,也适用于其他 Angular 版本。

于 2018-11-04T15:03:51.153 回答
2

现在没有必要把事情复杂化!

在较新版本的 Angular 中,使管道不纯不会导致开发模式出现任何错误。我猜当前接受的答案中提到的错误与此问题有关,该问题已在 5.5(!)年前解决(发布此问题后不久)。

据我了解,Angular 现在用于IterableDiffer检测不纯管道返回的数组中的更改,就像它对出现在模板中的普通数组一样(使用默认更改检测策略时),因此它认为这不是问题当数组引用已更改但其内容未更改时。这意味着如果我们每次都生成一个新数组就不会出现任何错误,所以我们可以让管道不纯,这样就可以了。

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'sortByName',
  pure: false
})
export class SortByNamePipe implements PipeTransform {
  transform(value, queryString) {
    return value.filter(student => new RegExp(queryString).test(student.name));
  }
}
于 2021-08-15T08:11:04.453 回答
0

解决方法:在构造函数中手动导入管道并使用此管道调用转换方法

constructor(
private searchFilter : TableFilterPipe) { }

onChange() {
   this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

实际上你甚至不需要管道

于 2018-10-25T20:04:52.713 回答
0

添加到管道额外参数,并在数组更改后立即更改,即使使用纯管道,列表也会刷新

让项目的项目| 管道:参数

于 2019-01-27T01:51:44.367 回答
0

在这个用例中,我在 ts 文件中使用了我的 Pipe 进行数据过滤。与使用纯管道相比,它的性能要好得多。像这样在 ts 中使用:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}
于 2019-07-26T14:08:34.780 回答
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();
            } 
于 2020-05-18T16:58:41.797 回答