Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit c260e73

Browse files
caitppetebacondarwin
authored andcommitted
fix(ngRepeat): do not sort object keys alphabetically
BREAKING CHANGE: Previously, the order of items when using ngRepeat to iterate over object properties was guaranteed to be consistent by sorting the keys into alphabetic order. Now, the order of the items is browser dependent based on the order returned from iterating over the object using the `for key in obj` syntax. It seems that browsers generally follow the strategy of providing keys in the order in which they were defined, although there are exceptions when keys are deleted and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues The best approach is to convert Objects into Arrays by a filter such as https://github.com/petebacondarwin/angular-toArrayFilter or some other mechanism, and then sort them manually in the order you need. Closes #6210 Closes #10538
1 parent e5ad6d6 commit c260e73

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

src/ng/directive/ngRepeat.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@
2323
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
2424
* This may be useful when, for instance, nesting ngRepeats.
2525
*
26+
*
27+
* # Iterating over object properties
28+
*
29+
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
30+
* syntax:
31+
*
32+
* ```js
33+
* <div ng-repeat="(key, value) in myObj"> ... </div>
34+
* ```
35+
*
36+
* You need to be aware that the JavaScript specification does not define what order
37+
* it will return the keys for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
38+
* used to sort the keys alphabetically.)
39+
*
40+
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
41+
* when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
42+
* keys in the order in which they were defined, although there are exceptions when keys are deleted
43+
* and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
44+
*
45+
* If this is not desired, the recommended workaround is to convert your object into an array
46+
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
47+
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
48+
* or implement a `$watch` on the object yourself.
49+
*
50+
*
2651
* # Special repeat start and end points
2752
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
2853
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
@@ -335,14 +360,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
335360
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
336361
} else {
337362
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
338-
// if object, extract keys, sort them and use to determine order of iteration over obj props
363+
// if object, extract keys, in enumeration order, unsorted
339364
collectionKeys = [];
340365
for (var itemKey in collection) {
341-
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') {
366+
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
342367
collectionKeys.push(itemKey);
343368
}
344369
}
345-
collectionKeys.sort();
346370
}
347371

348372
collectionLength = collectionKeys.length;
@@ -438,4 +462,3 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
438462
}
439463
};
440464
}];
441-

test/ng/directive/ngRepeatSpec.js

+20-19
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ describe('ngRepeat', function() {
163163
'</ul>')(scope);
164164
scope.items = {age:20, wealth:20, prodname: "Bingo", dogname: "Bingo", codename: "20"};
165165
scope.$digest();
166-
expect(element.text()).toEqual('age:20|codename:20|dogname:Bingo|prodname:Bingo|wealth:20|');
166+
expect(element.text()).toEqual('age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|');
167167
});
168168

169169
describe('track by', function() {
@@ -587,7 +587,7 @@ describe('ngRepeat', function() {
587587
'</ul>')(scope);
588588
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
589589
scope.$digest();
590-
expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|');
590+
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|');
591591
});
592592

593593

@@ -656,10 +656,10 @@ describe('ngRepeat', function() {
656656
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
657657
scope.$digest();
658658
expect(element.text()).
659-
toEqual('doug:d:true-false-false|' +
660-
'frodo:f:false-true-false|' +
661-
'misko:m:false-true-false|' +
662-
'shyam:s:false-false-true|');
659+
toEqual('misko:m:true-false-false|' +
660+
'shyam:s:false-true-false|' +
661+
'doug:d:false-true-false|' +
662+
'frodo:f:false-false-true|');
663663

664664
delete scope.items.doug;
665665
delete scope.items.frodo;
@@ -681,15 +681,15 @@ describe('ngRepeat', function() {
681681
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
682682
scope.$digest();
683683
expect(element.text()).
684-
toBe('doug:d:true-false|' +
685-
'frodo:f:false-true|' +
686-
'misko:m:true-false|' +
687-
'shyam:s:false-true|');
684+
toBe('misko:m:true-false|' +
685+
'shyam:s:false-true|' +
686+
'doug:d:true-false|' +
687+
'frodo:f:false-true|');
688688

689689
delete scope.items.frodo;
690690
delete scope.items.shyam;
691691
scope.$digest();
692-
expect(element.text()).toBe('doug:d:true-false|misko:m:false-true|');
692+
expect(element.text()).toBe('misko:m:true-false|doug:d:false-true|');
693693
});
694694

695695

@@ -700,11 +700,12 @@ describe('ngRepeat', function() {
700700
'</ul>')(scope);
701701
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
702702
scope.$digest();
703+
703704
expect(element.text()).
704-
toEqual('doug:d:true-false-false|' +
705-
'frodo:f:false-true-false|' +
706-
'misko:m:false-true-false|' +
707-
'shyam:s:false-false-true|');
705+
toEqual('misko:m:true-false-false|' +
706+
'shyam:s:false-true-false|' +
707+
'doug:d:false-true-false|' +
708+
'frodo:f:false-false-true|');
708709
});
709710

710711

@@ -716,10 +717,10 @@ describe('ngRepeat', function() {
716717
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
717718
scope.$digest();
718719
expect(element.text()).
719-
toEqual('doug:d:true-false|' +
720-
'frodo:f:false-true|' +
721-
'misko:m:true-false|' +
722-
'shyam:s:false-true|');
720+
toEqual('misko:m:true-false|' +
721+
'shyam:s:false-true|' +
722+
'doug:d:true-false|' +
723+
'frodo:f:false-true|');
723724
});
724725

725726

0 commit comments

Comments
 (0)