問題と可能な解決策を完全に理解するには、パイプとコンポーネントの角度変化検出について説明する必要があります。
パイプ交換検出
ステートレス/ピュアパイプ
デフォルトでは、パイプはステートレス/純粋です。ステートレス/ピュアパイプは、単に入力データを出力データに変換します。彼らは何も覚えていないので、プロパティはなく、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の回答によれば、「このエラーは開発モードでのみ発生します(ベータ0以降ではデフォルトで有効になっています。enableProdMode()
アプリをブートストラップするときに呼び出した場合、エラーはスローされません。」状態のドキュメントApplicationRef.tick()
:
開発モードでは、tick()は2回目の変更検出サイクルも実行して、それ以上の変更が検出されないようにします。この2番目のサイクル中に追加の変更が検出された場合、アプリのバインディングには、1回の変更検出パスでは解決できない副作用があります。この場合、Angularアプリケーションはすべての変更検出を完了する必要がある1つの変更検出パスしか持てないため、Angularはエラーをスローします。
私たちのシナリオでは、エラーは偽り/誤解を招くものだと思います。ステートフルパイプがあり、呼び出されるたびに出力が変化する可能性があります。これには副作用があり、問題ありません。NgForはパイプの後で評価されるため、正常に動作するはずです。
ただし、このエラーがスローされて実際に開発することはできないため、1つの回避策は、パイプの実装に配列プロパティ(つまり、状態)を追加し、常にその配列を返すことです。このソリューションについては、@ 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のイディオムになります。配列がパイプにデータを供給していて、配列が変わる可能性がある場合(配列内の項目であり、配列参照ではない)、ステートフルパイプを使用する必要があります。
pure:false
パイプとchangeDetection: ChangeDetectionStrategy.OnPush
コンポーネントに追加します。