javascript - last - ng repeat start track by




トラックバイng-repeatでの関数によりInfinite $ digest-loopが発生する (2)

どうやら私はng-repeat$$hashKeystrack by背後にあるメカニズムをまだ理解していませんでしtrack by

私は現在私のプロジェクトでAngularJS 1.6を使用しています。

問題:

私は自分のビューでリストをレンダリングするために使用したい複雑なオブジェクトの配列を手に入れました。 しかし、必要な結果を得るには、まずこれらのオブジェクトを修正(またはマッピング/拡張/変更)する必要があります。

const sourceArray = [{id: 1, name: 'Dave'}, {id:2, name: Steve}]

const persons = sourceArray.map((e) => ({enhancedName: e.name + e.id})) 

//Thus the content of persons is:
//[{enhancedName: 'Dave_1'}, {enhancedName: 'Steve_2'}]

これをビューにバインドすると、次のように機能します。

<div ng-repeat="person in ctrl.getPersons()">
    {{person.enhancedName}}
</div>

ただし、これは明らかに$digest()ループになります。これは、 .mapが呼び出されるたびに.mapが新しいオブジェクトインスタンスを返すためです。 関数を介してこれをng-repeatにバインドしているので、 $digestごとに再評価されます。モデルは安定せず、Angularは$digest -cyclesを再実行し続けます。これらのオブジェクトには$dirtyフラグが付けられます。

なぜ私は混乱しています

これは新しい問題ではなく、これに対するいくつかの解決策があります。

2012年のAngular-Issueで、 Igor Minar自身が、生成されたオブジェクトが同じであることをangleに伝えるために$$ hashKey-Propertyを手動で設定することを提案しました。 Thisは彼の作業中のフィドルですが、この非常に些細な例でも私のプロジェクトで使用したときにはまだ$digestループに出くわしたので、フィドル内でAngular-Versionをアップグレードしてみました。 どういうわけかそれはクラッシュします。

さて... Angular 1.3以降、この問題を正確に解決するための方法をtrack byた。 しかし両方

<div ng-repeat="person in ctrl.getPersons() track by $index">   

そして

<div ng-repeat="person in ctrl.getPersons() track by person.enhancedName">   

$digestループでクラッシュします。 私は、 track by statementはそれが同じオブジェクトで動作することをAngularに信じさせるべきであるという印象の下にありました、しかし明らかにこれはそれらが変更についてそれらをチェックし続けるのでそうではありません。 正直に言うと、私はこの原因を正しくデバッグできる方法がわかりません。

質問:

ng-repeatのデータソースとしてフィルタをかけた/変更した配列を使うことは可能ですか?

変更した配列を自分のコントローラに保存する必要はありません。データを常に更新する必要があり、データバインディングに頼るのではなく、コントローラで手動で維持および更新する必要があるためです。


あなたが提供した " それがクラッシュする "フィドルは、私にとって無限のダイジェストを生成しませんでした。 実際には、Angularアプリをブートストラップすることにも成功していません(ブートストラップは最新のAngularではそのようにはできません)。

私が理解したAngularブートストラップメカニズムを使うように書き直しました。 あなたが言ったように、それはクラッシュを再現します。

私はそれを文字列化されたJSONでうまく追跡する方法を見つけました。

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>

<script>
angular.module('myApp',[])
.controller('Ctrl', ['$scope', function($scope) {
    angular.extend($scope, {
    stringify: function(x) { return JSON.stringify(x) },
    getList: function() {
      return [
        {name:'John', age:25},
        {name:'Mary', age:28}
      ];
    }
  });
}]);
</script>

<div ng-app="myApp">

<div ng-controller="Ctrl">
  I have {{getList().length}} friends. They are:
  <ul>
    <li ng-repeat="friend in getList() track by stringify(friend)">
      [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
    </li>
  </ul>
</div>

</div>

つまり、追跡関数stringify()を提供します。 おそらくそのためのAngular組み込みもあります。

track by $indexも同様にうまくいきました - あなたの発見に反して。 JsFiddleが実験を少し台無しにしたと思います*

※以下は逸話です。 私はJsFiddle自体に問題があると思います 。 例えばtrack by stringify()track by stringify()私のtrack by stringify()私がFiddleをフォークして新しいブラウジングコンテキストで同じコードをもう一度試すまではうまくいきませんでした。 私はこれまでに無限のダイジェストを取得したらすぐに、そのJsFiddleが常に無限のダイジェストになると信じていました。 以前の実行から長引いていたいくつかのステートフルさがあったようでした。 それで、私はあなたが見ものがJsFiddle失敗するならば 、あなたは新しいJsFiddleで再び試みることを勧めます。

なぜあなたの$$hashKeyトリックが無限のダイジェストを導いたのか - 私はAngularが$$hashKeyが関数であることを期待していないと思う。 そのため、おそらくあなたの関数を呼び出す代わりに、 $$hashKey割り当てられた関数の参照比較を行い$$hashKey

getList()を呼び出すたびに$$hashKey新しいインスタンスを割り当てるので、参照が後続のダイジェストで等しくなることはなく、したがってダイジェストは永遠に試行され続けることになります。

編集 :HTTPS CDNを使用するようにの埋め込みとJsFiddleを更新しました(混合コンテンツセキュリティの問題を回避するため)。


同じ要素があっても、監視式getPersons()新しい配列を返す限り、 ===比較を使用する$digestサイクルは停止できません。 式track by関係なく、 ngRepeat 変更検出後にレンダリングノードにngRepeatます。

(function() {
  angular
    .module('app', [])
    .controller('AppController', AppController)

  function AppController($interval) {
    // you may have more performant options here
    const hashFn = angular.toJson.bind(angular)
    // your mapping logic for presentation
    const mapFn = (e) => ({
      enhancedName: e.name + e.id
    })

    // initialization of data
    let sourceArray = [{
      id: 1,
      name: 'Dave'
    }, {
      id: 2,
      name: 'Steve'
    }]

    // initialization of "cache"
    let personList = sourceArray.map(mapFn),
        lastListHash = hashFn(sourceArray)

    Object.defineProperty(this, 'personList', {
      get: function() {
        const hash = hashFn(sourceArray)
        if (hash !== lastListHash) {
          personList = sourceArray.map(mapFn)
          lastListHash = hash
        }

        // you need to return **the same** array
        // if the source has not been updated
        // to make `$digest` cycle happy
        return personList
      }
    })

    // test of changes
    $interval(() => sourceArray.push({
      id: Date.now(),
      name: 'a'
    }), 1000)
  }
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="app">

  <div ng-controller="AppController as ctrl">
    There are {{ctrl.personList.length}} persons.
    <ul>
      <li ng-repeat="person in ctrl.personList track by $index">
        [{{$index + 1}}] {{ person.enhancedName }}
      </li>
    </ul>
  </div>

</div>





angularjs-ng-repeat