AngularJSマジック:プリミティブでバインディングをハングさせない

AngularJSマジック:プリミティブでバインディングをハングさせない



AngularJSを使用する場合、「プリミティブにバインディングをハングさせないでください」というルールに繰り返し遭遇する可能性が高いでしょう。 この投稿では、プリミティブを使用すると問題が発生する詳細な例を見ていきます。各要素が文字列に関連付けられている要素のリストを作成します。



私たちの例


書籍を使用するアプリケーションで作業しており、各書籍にタグのリストがあるとします。 ユーザーにタグを編集する機能を与える素朴な方法は次のとおりです。

<div ng-controller="bookCtrl"> <div ng-repeat="tag in book.tags"> <input type="text" ng-model="tag"> </div> </div>
      
      







(おそらく、新しいタグを追加するために別のフィールドを追加し、既存のタグを削除するためのボタンを追加する必要がありますが、簡単にするためにこれを無視します。)



この例のデモはこちらから入手できます 。 入力フィールドのいずれかを編集してみてください。 すべてがうまくいくように思えます。 しかし、これはそうではありません。 よく見ると、行った変更がbook.tags配列と同期していないことがわかります。



これは、ng-repeatが各タグの子スコープを作成し、実際にはスコープが次のように見えるためです。

 bookCtrl scope = { tags: [ 'foo', 'bar', 'baz' ] } ng-repeat child scopes: { tag: 'foo' }, { tag: 'bar' }, { tag: 'baz' }
      
      





これらの子スコープでは、ng-repeatはタグ値の双方向バインディングを作成しません。 つまり、最初のフィールドを変更すると、ng-modelは最初の子スコープを{tag: 'something'}に変更するだけで、これはブックオブジェクトに表示されません。



これで、プリミティブがどのようにあなたと悪い冗談をすることができるかを見てきました。 各タグに文字列ではなくオブジェクトを使用した場合、子スコープのタグはbook.tagsと同じインスタンスになり、値の変更(たとえばtag.name)が機能するため、すべてが機能します。 、2方向バインディングなしでも。



しかし、ここでオブジェクトを使用したくないと仮定します。 この場合の対処方法



失敗した試み


-わかった! -考えていただけますか。 -ng-repeatをより高いレベルのタグのリストに直接関連付けます! 試してみましょう:

 <div ng-controller="bookCtrl"> <div ng-repeat="tag in book.tags"> <input type="text" ng-model="book.tags[$index]"> </div> </div>
      
      





したがって、ng-modelをタグリスト内の必要な要素に直接関連付け、子スコープを参照しないことで、コードを機能させました。 まあ、ほとんど。 これで、入力中にリスト内の値が変更されます。 しかし、今では他の何かが間違っています。 あなたは自分で見ることができます 。 それをして、待ってます。



ご覧のとおり、文字を入力すると、入力フィールドがフォーカスを失います。 WTF?



これを非難します。 効率のために、ng-repeatはリスト内のすべての値を追跡し、変更された特定の要素を再描画します。



しかし、プリミティブ(たとえば、数字や文字列)はJavaScriptでは不変です。 したがって、それらを変更する必要がある場合、以前のインスタンスは破棄され、新しいインスタンスが使用されます。 したがって、プリミティブを変更すると、ng-repeatが再描画します。 私たちの場合、これは、古いものを取り除き、新しいものを追加すると、途中でフォーカスが失われることを意味します。



解決策


プリミティブ値に関係なく、ng-repeatがリスト内の要素を識別するようにする方法を見つける必要があります。 適切なオプションは、リスト上のプリミティブインデックスを使用することです。 しかし、リスト内のアイテムを追跡するためにng-repeatを教える方法は?



幸いなことに、Angular 1.2にはオペレーターによるトラックが登場しました。

 <div ng-controller="bookCtrl"> <div ng-repeat="tag in book.tags track by $index"> <input type="text" ng-model="book.tags[$index]"> </div> </div>
      
      





ng-repeatがプリミティブ自体のリスト内のプリミティブのインデックスを使用するようになったため、これで問題が解決します。 つまり、ng-repeatは、インデックスが同じままであるため、値を変更するたびにタグの値を再描画しなくなりました。 あなたは自分で見ることができます



実際、トラックによる追跡はアプリケーションのパフォーマンス向上させるのに非常に役立ちますが 、上記の回避策について知ることも役立ちます。 そして、私の意見では、これはAngularの魔法をよりよく理解するのに役立ちます。



翻訳者からのメモ:5月29日に、AngularJSに捧げられた集会がモスクワで開催され、このトピックが議論されます。



All Articles