ng-repeat изменение порядка сортировки для всех элементов при добавлении нового элемента в модель

Я создал директиву «тесселейт», которая позволяет обернуть несколько элементов div.

<tessellate columns="4">
  <div class="thumbnail" ng-repeat="item in items track by item.id">
      {{item.Name}}<br />
      {{item.Summary}}
  </div>
</tessellate>

Он берет один div за раз и добавляет его к кратчайшему из указанного количества столбцов, чтобы создать эффект тесселяции/мозаики.

См. plunkr здесь: http://plnkr.co/edit/ur0bVCFRSz1UbRHeLjz8?p=preview

Проблема в том, что когда модель изменяется, ng-repeat использует порядок появления элементов в DOM вместо порядка в модели для перерисовки элементов. Вы можете видеть, что элементы сначала сортируются правильно, а после нажатия Add он сортирует элементы из первого столбца по горизонтали, затем элементы из следующего столбца и т. д.

Как запретить ng-repeat использовать порядок DOM для перерисовки элементов? Я уже пытался добавить orderBy item.id, но это не помогло.

var app = angular.module('app', []);

app.controller('itemController', ['$scope', function ($scope) {
    $scope.items = [
             { id:"1", Name:"Item1", Summary:"This is the summary of Item1" },
             { id:"2", Name:"Item2", Summary:"This is the summary of Item2. Some extra text on item two to test different heights." },
             { id:"3", Name:"Item3", Summary:"This is the summary of Item3" },
             { id:"4", Name:"Item4", Summary:"This is the summary of Item4. Some extra text on item four to test different heights." },
             { id:"5", Name:"Item5", Summary:"This is the summary of Item5. Some extra text on item five to test different heights. Some extra text on item to test different heights." },
             { id:"6", Name:"Item6", Summary:"This is the summary of Item6" },
             { id:"7", Name:"Item7", Summary:"This is the summary of Item7. Some extra text on item seven to test different heights." },
             { id:"8", Name:"Item8", Summary:"This is the summary of Item8" },
             { id:"9", Name:"Item9", Summary:"This is the summary of Item9. Some extra text on item nine to test different heights." },
             { id:"10", Name:"Item10", Summary:"This is the summary of Item10. Some extra text on item ten to test different heights." },
             { id:"11", Name:"Item11", Summary:"This is the summary of Item11" },
             { id:"12", Name:"Item12", Summary:"This is the summary of Item12. Some extra text on item to test different heights." },
             { id:"13", Name:"Item13", Summary:"This is the summary of Item13" },
             { id:"14", Name:"Item14", Summary:"This is the summary of Item14. Some extra text on item to test different heights." },
             { id:"15", Name:"Item15", Summary:"This is the summary of Item15. Some extra text on item to test different heights. Some extra text on item to test different heights." },
             { id:"16", Name:"Item16", Summary:"This is the summary of Item16" },
             { id:"17", Name:"Item17", Summary:"This is the summary of Item17. Some extra text on item to test different heights." },
             { id:"18", Name:"Item18", Summary:"This is the summary of Item18" }
             ];
    $scope.inc = $scope.items.length;
    $scope.add = function() {
        $scope.inc = $scope.inc + 1;
        $scope.items.push({ id: $scope.inc, Name: "New Item" + $scope.inc, Summary:"New Summary" });
    };
}]);

app.directive('tessellate', [function () {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {
            columns: '='
        },
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            $scope.numberToArray = function (num) {
                return new Array(num);
            };
        }],
        link: function (scope, elem, attrs, ctrl) {

            scope.$watch(function () {
                return elem.children().first().height();
            }, function (height) {
                if (height > 0) {
                    var containers = elem.children();
                    var transcludedDivsContainer = containers.first();
                    var targetColumns = containers.eq(1).children();

                    // Add the transcluded divs one at a time into the shortest column.
                    angular.forEach(transcludedDivsContainer.children(), function (div) {
                        var shortCol = null;
                        angular.forEach(targetColumns, function (col) {
                            col = angular.element(col);
                            if (shortCol === null || col.height() < shortCol.height()) {
                                shortCol = col;
                            }
                        });
                        shortCol.append(div);
                    });
                }
            }
            );
        },
        templateUrl: "tessellateTemplate.html"
    };
}]);

person adam0101    schedule 09.04.2014    source источник
comment
Я не верю, что проблема заключается в ng-repeat. Источник проблемы в вашей директиве. Директива берет элементы в порядке их DOM и устанавливает их соответствующим образом, в то время как ng-repeat помещает их в правильном порядке. Просто добавьте {{$index}} рядом с именем (например, {{item.Name}}--||--{{$index}}) и сами убедитесь, что индексация хорошая. Я не слишком углублялся в вашу директиву, но конфликт возникает из-за этого, как я вижу, вы смотрите на структуру DOM для сортировки элементов.   -  person yccteam    schedule 10.04.2014
comment
@yccteam, но если я пройдусь по коду, я увижу, что порядок уже перепутался еще до того, как мои часы $watch попадут в цель. Я считаю, что ng-repeat сканирует DOM, чтобы получить существующие элементы, поэтому ему не нужно их воссоздавать, но предполагается, что элементы находятся в том же порядке, что и при первом запуске.   -  person adam0101    schedule 10.04.2014
comment
Ты прав. Это сценарий, который я никогда раньше не пробовал, но Бен объясняет его в этом запись в блоге   -  person yccteam    schedule 10.04.2014


Ответы (1)


Я разветвил ваш plunkr и возился с ним. Я думаю, теперь это работает так, как вы хотите.

http://plnkr.co/edit/1y8jE0SLuJK6XTNRBKF3?p=preview

В основном это исправило сортировку списка элементов dom по их индексу, и для этого я добавил $index к атрибуту индекса данных элемента.

person aet    schedule 10.04.2014
comment
пока с моего телефона все выглядит хорошо, я проверю на своем рабочем столе, когда вернусь к своему столу. Как вы узнали, что нужно установить такой атрибут индекса? - person adam0101; 10.04.2014
comment
о, неважно. Я думал, вы переопределяете какое-то поведение ng-repeat, но я вижу, что вы сами используете индекс для сортировки элементов. Это прекрасно работает, но я удивлен, что ng-repeat удаляет и заменяет все элементы, даже если изменился только один из них. Вы знаете, это нормально или это что-то, что этот код заставляет делать ng-repeat? Мне интересно, можно ли его настроить, чтобы влиять только на новые или обновленные элементы для повышения производительности. - person adam0101; 10.04.2014
comment
Глядя на код ng-repeat, кажется, что он просто работает с изменениями. Элементы, которые все еще находятся в списке, не должны быть удалены/повторно добавлены в дом. Чтобы ускорить тесселяцию, вам, возможно, придется использовать некоторую технику кэширования, например, то, что делает ng-repeat. Прямо сейчас он перестраивает весь список каждый раз, когда что-то добавляется. Потребуется сохранить какое-то представление мозаичного состояния, восстановить его, а затем добавить новый элемент, не пересчитывая его каждый раз. - person aet; 10.04.2014
comment
Что ж, этого мне вполне достаточно. Хотя, и я знаю, что это отдельный вопрос, если вы удалите элемент из массива, порядок все равно перепутается. - person adam0101; 10.04.2014