[解決済み] Angular2でNgForが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;
}
}
どのように解決するのですか?
この問題と可能な解決策を完全に理解するために、Angularの変更検出 -- パイプとコンポーネントのための -- について説明する必要があります。
パイプの変更検出
ステートレス/ピュアパイプ
デフォルトでは、パイプはステートレス/ピュアです。ステートレス/ピュアパイプは、単に入力データを出力データに変換するだけです。 何も覚えていないので、プロパティはありません。
transform()
メソッドを持つだけです。 Angularはそのため、ステートレス/ピュアパイプの処理を最適化できます。入力が変化しないのであれば、パイプは変化検出サイクルの間に実行される必要はないのです。次のようなパイプの場合
{{power | exponentialStrength: factor}}
,
power
と
factor
は入力です。
この質問に対して
"#student of students | sortByName:queryElem.value"
,
students
と
queryElem.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のイディオムになるでしょう。配列がパイプに供給され、配列が変わるかもしれない場合(配列の参照ではなく、配列内のアイテム)、ステートフルパイプを使用する必要があります。
関連
-
[解決済み] インデックスを属性値とするngFor
-
[解決済み] コンポーネントクラスからテンプレート参照変数にアクセスする
-
[解決済み] Angular 2: 反応するフォームコントロールの反復処理
-
[解決済み] Angular Materialでのデフォルトのソート - ヘッダーのソート
-
[解決済み] パイプ ' ' が見つかりません。
-
[解決済み] angular-cliのビルドでカスタムファイルをインクルードするには?
-
[解決済み] Angular v5からAngular v6にプロジェクトをアップグレードしたい。
-
[解決済み] Angular2 bodyタグにクラスを追加する
-
[解決済み] AngularでFormArrayからすべての項目を削除する
-
[解決済み] Karma/Jasmineのテストで「[object ErrorEvent] thrown」エラーが発生した場合、どのようにデバッグすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] ngForにフィルターをかけるには?
-
[解決済み] チェックした後に○○の表現が変わっている
-
[解決済み】AngularJSの$watchに相当するAngularは何ですか?
-
[解決済み] ng buildの後にangular-cliのdist-folderのパスを変更するには?
-
[解決済み] Angular 2: 反応するフォームコントロールの反復処理
-
[解決済み] Angular Materialでのデフォルトのソート - ヘッダーのソート
-
[解決済み] angular-cliのパラメータ --base-href と --deploy-url の違いは何ですか?
-
[解決済み] Angular2 bodyタグにクラスを追加する
-
[解決済み] AngularでFormArrayからすべての項目を削除する
-
[解決済み] RxJSのmap演算子でエラーを出す方法 (angular)