Функция в ng-повторе с треком по причинам Бесконечный $digest-loop

По-видимому, я еще не понял механика за ng-repeat, $$hashKeys и 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() -loop, потому что .map возвращает новые объекты-экземпляры каждый раз, когда он вызывается. Поскольку я привязываю это к ng-repeat через функцию, он переоценивается в каждом $digest, модель не стабилизируется, а Angular сохраняет повторные t $digest -циклы, потому что эти объекты помечены как $dirty.

Почему я запутался

Теперь это не новая проблема, и для этого есть несколько решений:

В a Angular -Issue с ​​2012 года Игорь Минар сам предложил установить $$ hashKey-Property вручную, чтобы сообщить Angular, что сгенерированные объекты одинаковы. Это является его рабочей скрипкой, но так как даже этот очень простой пример по-прежнему сталкивался с $digest -loop, когда я использовал его в своем проекте, я попробовал модернизировать 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">   

сбой -loop. У меня создалось впечатление, что оператор track by должен позволить Angular полагать, что он работает с одними и теми же объектами, но, видимо, это не так, поскольку он просто проверяет их на наличие изменений. Честно говоря, я понятия не имею, как я могу правильно отладить причину этого.

Вопрос:

Можно ли использовать фильтрованный/модифицированный массив в качестве источника данных для ng-repeat?

Я не хочу хранить модифицированный массив на моем контроллере, потому что мне нужно постоянно обновлять его данные, а затем поддерживать и обновлять его вручную в контроллере вместо того, чтобы полагаться на привязку данных.

Ответ 1

" он сработает" скрипка, которую вы предоставили, не создала для меня бесконечный дайджест. Фактически: он даже не загрузил загрузочное приложение Angular (похоже, что самонастройка не может быть выполнена таким образом в последнем Angular).

I переписал его, чтобы использовать механизм загрузки 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>

Ответ 2

Пока наблюдаемое выражение getPersons() возвращает массив new, даже с теми же элементами, цикл $digest, который использует сравнение ===, не может останавливаться; независимо от выражения track by, которое входит в игру для узлов рендеринга, после изменить обнаружение 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>