首页 文章

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

提问于
浏览
104

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

7 回答

  • 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() 方法 . 即使输入没有改变,也可能需要对它们进行评估 . 当我们指定管道是有状态/非纯粹的时候 - 那么每当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,“此错误仅在开发模式下发生(默认情况下从beta-0开始) . 如果在引导应用程序时调用 enableProdMode() ,则不会抛出错误 . ” docs for ApplicationRef.tick()州:

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

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

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

    但是,我们可以更高效,并且正如我们将看到的,我们将不需要管道实现中的数组属性,并且我们不需要针对双重更改检测的变通方法 .

    组件更改检测

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

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

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

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


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

    Plunker

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

  • 25

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

    According to the angular2 guide on pipes

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

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

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

    来自angular documentation

    Pure and impure pipes

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

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

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

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

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

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

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

  • 9

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

    constructor(
    private searchFilter : TableFilterPipe) { }
    
    onChange() {
       this.data = this.searchFilter.transform(this.sourceData, this.searchText)}
    

    实际上你甚至不需要管道

  • 0

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

    它适用于角度6,也适用于其他角度版本 .

  • 0

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

    让项目项目|管:PARAM

相关问题