1. ホーム
  2. angular

[解決済み] Angular2でNgForがPipeでデータを更新しない。

2022-08-04 03:13:28

質問

このシナリオでは、生徒のリスト (配列) をビューに表示するために 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;
    }
}


どのように解決するのですか?

この問題と可能な解決策を完全に理解するために、Angularの変更検出 -- パイプとコンポーネントのための -- について説明する必要があります。

パイプの変更検出

ステートレス/ピュアパイプ

デフォルトでは、パイプはステートレス/ピュアです。ステートレス/ピュアパイプは、単に入力データを出力データに変換するだけです。 何も覚えていないので、プロパティはありません。 transform() メソッドを持つだけです。 Angularはそのため、ステートレス/ピュアパイプの処理を最適化できます。入力が変化しないのであれば、パイプは変化検出サイクルの間に実行される必要はないのです。次のようなパイプの場合 {{power | exponentialStrength: factor}} , powerfactor は入力です。

この質問に対して "#student of students | sortByName:queryElem.value" , studentsqueryElem.value は入力であり、パイプ sortByName は無国籍/純粋である。 students は配列(参照)です。

  • 生徒が追加されると、配列 参照 は変更されません -。 students は変更されません - したがって、ステートレス/ピュアパイプは実行されません。
  • フィルタ入力に何かが入力されたとき。 queryElem.value は変化するので、ステートレス/ピュアパイプが実行されます。

配列の問題を解決する1つの方法は、生徒が追加されるたびに配列の参照を変更することです。これを行うには 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 さんの回答 このエラーはdevモード(beta-0ではデフォルトで有効)でのみ発生します。もしあなたが enableProdMode() を呼び出すと、このエラーは発生しません。 のドキュメントは ApplicationRef.tick() の状態になります。

開発モードでは、tick() は 2 回目の変更検出サイクルも実行し、それ以上の変更が検出されないことを確認します。この2回目のサイクルで追加の変更が検出された場合、アプリ内のバインディングには、1回の変更検出パスでは解決できない副作用があります。この場合、Angularはエラーを投げます。Angularアプリケーションは1つの変更検出パスしか持つことができず、その間にすべての変更検出を完了する必要があるからです。

このシナリオでは、このエラーは不正確または誤解を招くものだと思います。 ステートフル パイプがあり、呼び出されるたびに出力が変更される可能性があります。 NgForはパイプの後で評価されるので、問題なく動作するはずです。

しかし、このエラーが投げられたままでは開発できないので、1つの回避策として、パイプの実装に配列プロパティ(つまり状態)を追加し、常にその配列を返すようにします。 この解決策については、@pixelbits の回答を参照してください。

しかし、もっと効率的にすることができます。これから説明するように、パイプの実装では配列プロパティが不要になり、二重変化検出の回避策も不要になります。

コンポーネントの変更検出

デフォルトでは、すべてのブラウザイベントにおいて、Angularの変更検出はすべてのコンポーネントに変更がないか調べます - 入力とテンプレート(そしておそらく他のものも)がチェックされます。

コンポーネントが入力プロパティ(およびテンプレートイベント)にのみ依存し、入力プロパティが不変であることが分かっている場合、より効率的な onPush という変更検出方法を使用できます。この方法では、すべてのブラウザ イベントでチェックするのではなく、入力が変更されたときとテンプレート イベントがトリガーされたときにのみコンポーネントをチェックします。 そして、どうやら私たちは、その Expression ... has changed after it was checked というエラーは発生しません。 これは onPush コンポーネントが "marked" されるまで再チェックされないからです ( ChangeDetectorRef.markForCheck() ) までチェックされません。 つまり、テンプレートバインディングとステートフルパイプの出力は一度だけ実行され、評価されます。 ステートレス/ピュアパイプは入力が変更されない限り実行されないことに変わりはありません。 ですから、ここでもやはりステートフルパイプが必要なのです。

これは @EricMartinez が提案した解決策です: ステートフルパイプに onPush の変更検出です。 この解決策については、@caffinatedmonkey の回答を参照してください。

この解決策では transform() メソッドは毎回同じ配列を返す必要はありません。 状態を持たないステートフルパイプというのはちょっと変ですね。 さらに考えてみると、ステートフル・パイプはおそらく常に同じ配列を返すべきでしょう。 そうでなければ、このパイプは onPush コンポーネントでのみ使用できます。


というわけで、@Eric と @pixelbits の答えの組み合わせが好きだと思います。 onPush の変更検出が可能なコンポーネントであることです。 ステートフルパイプは同じ配列の参照を返すので、このパイプはまだ onPush .

プランカー

これはおそらくAngular 2のイディオムになるでしょう。配列がパイプに供給され、配列が変わるかもしれない場合(配列の参照ではなく、配列内のアイテム)、ステートフルパイプを使用する必要があります。