Как перебирать элементы, возвращаемые функцией с помощью ng-repeat?

114

Я хочу многократно создавать div, элементы - это объекты, возвращаемые функцией. Однако следующий код сообщает об ошибках: Достигнуто 10 итераций $ digest (). Прерывание! jsfiddle здесь: http://jsfiddle.net/BraveOstrich/awnqm/

<body ng-app>
  <div ng-repeat="entity in getEntities()">
    Hello {{entity.id}}!
  </div>
</body>
Сяо Пэн - ZenUML.com
источник

Ответы:

195

Краткий ответ : действительно ли вам нужна такая функция или можно использовать собственность? http://jsfiddle.net/awnqm/1/

Длинный ответ

Для простоты опишу только ваш случай - ngRepeat для массива объектов. Также я опущу некоторые детали.

AngularJS использует грязную проверку для обнаружения изменений. При запуске приложения оно работает $digestдля $rootScope. $digestвыполнит обход в глубину для иерархии области видимости . У всех прицелов есть список часов. Каждые часы имеют последнее значение (изначально initWatchVal). Для каждой области видимости для всех часов $digestон запускается, получает текущее значение ( watch.get(scope)) и сравнивает его с watch.last. Если текущее значение не равно watch.last(всегда для первого сравнения), $digestустанавливается dirtyв true. Когда все области видимости обрабатываются, if dirty == true $digestначинает еще один обход в глубину из $rootScope. $digestзаканчивается, когда dirty == false или количество обходов == 10. В последнем случае выдается ошибка «Достигнуто 10 итераций $ digest ()». будет зарегистрирован.

Теперь о ngRepeat. Для каждого watch.getвызова он сохраняет объекты из коллекции (возвращающее значение getEntities) с дополнительной информацией в кеше ( HashQueueMapby hashKey). При каждом watch.getвызове ngRepeatпытается получить объект hashKeyиз кеша. Если он не существует в кеше, ngRepeatсохраняет его в кеше, создает новую область видимости, помещает в нее объект, создает элемент DOM и т . Д.

Теперь о hashKey. Обычно hashKeyэто уникальный номер, генерируемый nextUid(). Но это может быть функция . hashKeyпосле создания сохраняется в объекте для использования в будущем.

Почему ваш пример генерирует ошибку : функция getEntities()всегда возвращает массив с новым объектом. У этого объекта нет hashKeyи не существует в ngRepeatкеше. Таким образом, ngRepeatкаждый watch.getгенерирует для него новые возможности с новыми часами {{entity.id}}. Это смотрела на первых watch.getпорах watch.last == initWatchVal. Итак watch.get() != watch.last. Итак, $digestначинается новый траверс. Таким образом ngRepeatсоздается новая сфера применения новых часов. Итак ... после 10 ходов вы получаете ошибку.

Как это исправить

  1. Не создавайте новые объекты при каждом getEntities()вызове.
  2. Если вам нужно создать новые объекты, вы можете добавить hashKeyдля них метод. Примеры см. В этой теме .

Надеюсь, люди, знающие внутренности AngularJS, поправят меня, если я в чем-то ошибаюсь.

Артем Андреев
источник
4
+1 спасибо за это. У меня была такая же проблема, и я не мог использовать для нее статическое свойство. $$ hashKey действительно должен быть задокументирован на странице ngRepeat руководства IMO.
Майкл Мусса
Есть идеи, что изменилось с 1.1.3 на 1.1.4, что повлияло на это? До 1.1.4 это действительно работало. В журнале изменений нет ничего об этом, и я не могу понять, в чем разница. Текущее поведение имеет смысл.
m59 05
Кроме того, проверьте это, если сможете: stackoverflow.com/questions/20933261/… Я не уверен, что мой ответ - правильный путь или нет ..
m59 05
2
Таким образом, следуя рекомендациям, Do not create new objects on every getEntities() call.это можно легко исправить следующим образом:<div ng-repeat="entity in entities = (entities || getEntities())">
przno
2
решение из моего предыдущего комментария работает в том случае, когда getEntities()всегда возвращает один и тот же массив, если массив когда-либо изменится, вы не получите его вng-repeat
przno
44

Инициализировать массив вне повтора

<body ng-app>
   <div ng-init="entities = getEntities()">
       <div ng-repeat="entity in entities">
           Hello {{entity.id}}!
       </div>
   </div>
</body>
Mwayi
источник
8
Это не работает, если getEntities()в жизненном цикле программы возвращается что-то другое. Скажем, например, что getEntities()триггер $http.get. Когда get, наконец, разрешится (вы сделали вызов AJAX), entitiesон уже будет инициализирован.
Ночью,
3
Из угловой документацииThe only appropriate use of ngInit is for aliasing special properties of ngRepeat. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
Blowsie 09
Я думаю, что главное - «инициализировать массив вне повтора» любыми способами ... и @Nighto good call on promises
Mwayi
15

Об этом сообщили здесь и получили такой ответ:

Ваш метод получения не является идемпотентным и изменяет модель (путем создания нового массива при каждом его вызове). Это вынуждает angular продолжать вызывать его в надежде, что модель в конечном итоге стабилизируется, но он никогда этого не делает, поэтому angular отказывается и выдает исключение.

Значения, возвращаемые геттером, равны, но не идентичны, и в этом проблема.

Вы можете увидеть, как это поведение исчезнет, ​​если вы переместите массив за пределы главного контроллера:

var array = [{id:'angularjs'}];
function Main($scope) {
    $scope.getEntities = function(){return array;};
};

потому что теперь он каждый раз возвращает один и тот же объект. Возможно, вам придется перестроить вашу модель, чтобы использовать свойство в области вместо функции:

Мы работали над этим, назначая результат метода контроллера свойству и выполняя ng: repeat против него.

Деннис
источник
Использование свойства может быть единственным способом, если функция имеет параметр, изменяющийся на каждой итерации.
Стефан
7

На основе комментария @przno

<body ng-app>
  <div ng-repeat="item in t = angular.equals(t, getEntities()) ? t : getEntities()">
    Hello {{item.id}}!
  </div>
</body>

Кстати, второе решение, которое предлагает @Artem Andreev, не работает в Angular 1.1.4 и выше, а первое не решает проблему. Так что, боюсь, пока это менее острое решение без недостатков в функциональности.

Агат
источник
Вы имеете в виду item.id? Если вы имеете в виду entity.id, не могли бы вы объяснить? Большое спасибо!
Герфрид
Да, ты прав. Item.idэто то, что должно б. Спасибо
Агат