Функция в 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>