NgFor不使用Angular2中的Pipe更新数据

时间:2015-12-24 18:07:56

标签: angular angular2-template angular2-directives angular-pipe

在这种情况下,我使用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;
    }
}

9 个答案:

答案 0 :(得分:134)

为了完全理解问题和可能的解决方案,我们需要讨论角度变化检测 - 用于管道和组件。

换管检测

无状态/纯管道

默认情况下,管道是无状态/纯粹的。无状态/纯管道只是将输入数据转换为输出数据。他们不记得任何东西,所以他们没有任何属性 - 只是一种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's answer,&#34;此错误仅发生在开发模式(默认情况下从beta-0启用)。如果您在引导应用时调用enableProdMode(),则错误不会被抛出。&#34; docs for ApplicationRef.tick()州:

  

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

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

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

但是,我们可以提高效率,而且正如我们所看到的,我们在管道实现中不需要数组属性,并且我们不需要双变化检测的解决方法

组件更改检测

默认情况下,在每个浏览器事件中,Angular变化检测会遍历每个组件以查看它是否发生了变化 - 输入和模板(以及其他东西?)都会被检查。

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

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

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

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

Plunker

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

答案 1 :(得分:27)

正如Eric Martinez在评论中指出的那样,将pure: false添加到Pipe装饰者和changeDetection: ChangeDetectionStrategy.OnPush添加到Component装饰者将解决您的问题。 Here is a working plunkr.更改为ChangeDetectionStrategy.Always,也有效。这就是原因。

According to the angular2 guide on pipes

  

管道默认为无状态。我们必须通过将pure装饰器的@Pipe属性设置为false来声明管道是有状态的。此设置告诉Angular的变化检测系统在每个循环中检查该管道的输出,无论其输入是否已经改变。

对于ChangeDetectionStrategy,默认情况下,每个循环都会检查所有绑定。添加pure: false管道后,我认为由于性能原因,更改检测方法会从CheckAlways更改为CheckOnce。使用OnPush时,仅在输入属性更改或触发事件时才会检查Component的绑定。有关变更检测器的更多信息,angular2的重要部分,请查看以下链接:

答案 2 :(得分:19)

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

答案 3 :(得分:9)

来自angular documentation

纯净且不纯的管道

有两类管道:纯净和不纯净。管道默认是纯净的。到目前为止,你见过的每根烟斗都是纯净的。通过将其纯标志设置为false来使管道不纯。你可以让FlyingHeroesPipe不纯洁:

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

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

纯净的管道 Angular仅在检测到输入值的纯更改时才执行纯管道。纯变化是对原始输入值(String,Number,Boolean,Symbol)的更改或更改的对象引用(Date,Array,Function,Object)。

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

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

因此,当您可以使用变化检测策略时,最好使用纯管道。如果不能,可以使用不纯的管道。

答案 4 :(得分:0)

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

constructor(
private searchFilter : TableFilterPipe) { }

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

实际上,您甚至不需要管道

答案 5 :(得分:0)

不是执行pure:false。您可以使用this.students = Object.assign([],NEW_ARRAY);复制并替换组件中的值。其中NEW_ARRAY是修改后的数组。

它适用于角度6,也应该适用于其他角度版本。

答案 6 :(得分:0)

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

让项目项| pipe:参数

答案 7 :(得分:0)

在这种情况下,我使用ts文件中的Pipe进行数据过滤。与使用纯管道相比,它在性能上要好得多。在这样的ts中使用:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}

答案 8 :(得分: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();
            } 
相关问题