Объект сортировки AngularJs в ngRepeat

Я использую AngularJs и обнаружил проблему с упорядочением свойств хэш-объекта в шаблоне.
Мой объект похож:

function TestCtrl($scope){
    $scope.week = {'MONDAY': ['manuel'], 'TUESDAY': [], 'WEDNESDAY': ['valerio'], 'THURSDAY': ['manuel', 'valerio'], 'FRIDAY': []}
}

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

<div ng-repeat="(day, names) in week">
    <span>{{day}}</span>
    <ul> <li ng-repeat="name in names">{{name}}</li> </ul>
</div>

Порядок напечатанных дней отличается: FRIDAY MONDAY THURSDAY TUESDAY WEDNESDAY

Я попытался применить фильтр orderBy, но я думаю, что он не работает с объектами, а просто с массивами...

Как я могу его заказать?

Ответы

Ответ 1

Согласно AngularJS docs (версия 1.3.20):

Вы должны знать, что спецификация JavaScript не определяет в каком порядке он вернет ключи для объекта. Чтобы иметь гарантированный детерминированный порядок для ключей, версии Angular до и включая 1.3 сортировать клавиши в алфавитном порядке.

Обходной путь заключается в использовании массива ключей:

function TestCtrl($scope){
    $scope.week = {
        'MONDAY': ['manuel'], 'TUESDAY': [], 
        'WEDNESDAY': ['valerio'], 'THURSDAY': ['manuel', 'valerio'],    
        'FRIDAY': []}

    $scope.weekDays = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"];
}

Используйте массив для просмотра итерации:

<div ng-repeat="day in weekDays">
    <span>{{day}}</span>
    <ul> <li ng-repeat="name in week[day]">{{name}}</li> </ul>
</div>

Обновление от версии AngularJS 1.4.6 docs:

Версия 1.4 удалила сортировку по алфавиту. Теперь мы полагаемся на заказ возвращенный браузером при запуске for key in myObj.

Ответ 2

Невозможно заказать хеш-объекты, подобные этому. Не только в angular, но и в javascript вообще.

Я бы преобразовал хэш-объект в массив объектов, что-то вроде этого:

$scope.week = [{day: 'MONDAY', names: ['manuel']}, {day: 'TUESDAY', names: []} ...];

И затем измените представление на что-то подобное:

<div ng-repeat="day in week|orderBy:'day'">
    <span>{{day.day}}</span>
    <ul> <li ng-repeat="name in day.names">{{name}}</li> </ul>
</div>

Ответ 3

Это было исправлено в Angular 1.4. Как указано в официальной документации Angular ниже:

Версия 1.4 удалила сортировку по алфавиту. Теперь мы полагаемся на заказ возвращенный браузером при запуске for key in myObj

https://docs.angularjs.org/api/ng/directive/ngRepeat

Ответ 4

На самом деле существует простое решение... Клавиши объекта не упорядочены по умолчанию НО, если вы создаете объект в браузере с нуля, ваш браузер WILL знает порядок;)

Пример:

// test-1
var data = {};
data['a'] = 10;
data['b'] = 5;
data['c'] = 2;

Object.keys(data); // ["a", "b", "c"]


// test-2
var data = {};
data['b'] = 5;
data['a'] = 10;
data['c'] = 2;

Object.keys(data); // ["b", "a", "c"]

Так просто... воссоздайте объект... или используйте этот простой фильтр:

.filter('orderObject', function () {
    return function (object, reverse) {
        var keys = Object.keys(object || {}).sort();
        if (reverse) keys.reverse();
        for (var ordered = {}, i = 0; keys[i]; i++) {
            ordered[keys[i]] = object[keys[i]];
        }
        return ordered;
    }
})

Пример с регулярными объектами:

<!-- MARKUP : DEFAULT -->
<table>
    <tr ng-repeat="(key, value) in data">
        <td>{{key}}</td>
        <td>{{value}}</td>
    </tr>
</table>

<!-- RESULT : test-1 -->
<table>
    <tr>
        <td>a</td>
        <td>10</td>
    </tr>
    <tr>
        <td>b</td>
        <td>5</td>
    </tr>
    <tr>
        <td>c</td>
        <td>2</td>
    </tr>
</table>

<!-- RESULT : test-2 -->
<table>
    <tr>
        <td>b</td>
        <td>5</td>
    </tr>
    <tr>
        <td>a</td>
        <td>10</td>
    </tr>
    <tr>
        <td>c</td>
        <td>2</td>
    </tr>
</table>

Пример с отсортированными объектами:

<!-- MARKUP : with FILTER orderObject:<reverse?> -->
<table>
    <tr ng-repeat="(key, value) in data | orderObject">
        <td>{{key}}</td>
        <td>{{value}}</td>
    </tr>
</table>

<!-- RESULT : test-1 without reverse -->
<table>
    <tr>
        <td>a</td>
        <td>10</td>
    </tr>
    <tr>
        <td>b</td>
        <td>5</td>
    </tr>
    <tr>
        <td>c</td>
        <td>2</td>
    </tr>
</table>

<!-- RESULT : test-2 with reverse -->
<table>
    <tr>
        <td>c</td>
        <td>2</td>
    </tr>
    <tr>
        <td>b</td>
        <td>5</td>
    </tr>
    <tr>
        <td>a</td>
        <td>10</td>
    </tr>
</table>

Ответ 5

Этот вопрос старый, но в итоге я получил ответ на этот вопрос, который, как я думал, может быть улучшением некоторых предыдущих ответов.

Вместо того, чтобы просто преобразовать объект в массив, его намного больше DRY для создания фильтра angular, который делает это для вас, а затем ngRepeat или ngOptions.

В качестве примера:

angular.module('myproject')
    .filter('objOrder', function () {
        return function(object) {
            var array = [];
            angular.forEach(object, function (value, key) {
                array.push({key: key, value: value});
            });
            return array;
        };
    });

Затем с объектом вроде:

    $scope.degrees: {
        ASC: "Associate's",
        BAS: "Bachelor's",
        MAS: "Master's",
        MD: "M.D.",
        JD: "J.D.",
        PHD: "Ph.D",
        OTH: "Other"
    }

Мы могли бы использовать его так:

<select
    ng-model="myDegree"
    required
    ng-options="item.key as item.value for item in degrees | objOrder"
    >
</select>

Таким образом, вам не нужно создавать новый массив и загрязнять $scope, и вам не нужно возвращаться и изменять фактический объект degrees, который может иметь нежелательные побочные эффекты.