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

Commit d4d1031

Browse files
jbedardmgol
authored andcommitted
fix(ngRepeat): fix trackBy function being invoked with incorrect scope
Also fixes a leak of that scope across all further instances of the repeated element. Fixes #16776 Closes #16777
1 parent 7981140 commit d4d1031

File tree

2 files changed

+55
-20
lines changed

2 files changed

+55
-20
lines changed

src/ng/directive/ngRepeat.js

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,13 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
454454
return block.clone[block.clone.length - 1];
455455
};
456456

457+
var trackByIdArrayFn = function($scope, key, value) {
458+
return hashKey(value);
459+
};
460+
461+
var trackByIdObjFn = function($scope, key) {
462+
return key;
463+
};
457464

458465
return {
459466
restrict: 'A',
@@ -493,32 +500,23 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
493500
aliasAs);
494501
}
495502

496-
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
497-
var hashFnLocals = {$id: hashKey};
503+
var trackByIdExpFn;
498504

499505
if (trackByExp) {
500-
trackByExpGetter = $parse(trackByExp);
501-
} else {
502-
trackByIdArrayFn = function(key, value) {
503-
return hashKey(value);
504-
};
505-
trackByIdObjFn = function(key) {
506-
return key;
506+
var hashFnLocals = {$id: hashKey};
507+
var trackByExpGetter = $parse(trackByExp);
508+
509+
trackByIdExpFn = function($scope, key, value, index) {
510+
// assign key, value, and $index to the locals so that they can be used in hash functions
511+
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
512+
hashFnLocals[valueIdentifier] = value;
513+
hashFnLocals.$index = index;
514+
return trackByExpGetter($scope, hashFnLocals);
507515
};
508516
}
509517

510518
return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) {
511519

512-
if (trackByExpGetter) {
513-
trackByIdExpFn = function(key, value, index) {
514-
// assign key, value, and $index to the locals so that they can be used in hash functions
515-
if (keyIdentifier) hashFnLocals[keyIdentifier] = key;
516-
hashFnLocals[valueIdentifier] = value;
517-
hashFnLocals.$index = index;
518-
return trackByExpGetter($scope, hashFnLocals);
519-
};
520-
}
521-
522520
// Store a list of elements from previous run. This is a hash where key is the item from the
523521
// iterator, and the value is objects with following properties.
524522
// - scope: bound scope
@@ -572,7 +570,7 @@ var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $ani
572570
for (index = 0; index < collectionLength; index++) {
573571
key = (collection === collectionKeys) ? index : collectionKeys[index];
574572
value = collection[key];
575-
trackById = trackByIdFn(key, value, index);
573+
trackById = trackByIdFn($scope, key, value, index);
576574
if (lastBlockMap[trackById]) {
577575
// found previously seen block
578576
block = lastBlockMap[trackById];

test/ng/directive/ngRepeatSpec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,43 @@ describe('ngRepeat', function() {
400400
expect(element.find('input')[1].checked).toBe(true);
401401
expect(element.find('input')[2].checked).toBe(true);
402402
}));
403+
404+
it('should invoke track by with correct locals', function() {
405+
scope.trackBy = jasmine.createSpy().and.callFake(function(k, v) {
406+
return [k, v].join('');
407+
});
408+
409+
element = $compile(
410+
'<ul>' +
411+
'<li ng-repeat="(k, v) in [1, 2] track by trackBy(k, v)"></li>' +
412+
'</ul>')(scope);
413+
scope.$digest();
414+
415+
expect(scope.trackBy).toHaveBeenCalledTimes(2);
416+
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1]);
417+
expect(scope.trackBy.calls.argsFor(1)).toEqual([1, 2]);
418+
});
419+
420+
// https://github.com/angular/angular.js/issues/16776
421+
it('should invoke nested track by with correct locals', function() {
422+
scope.trackBy = jasmine.createSpy().and.callFake(function(k1, v1, k2, v2) {
423+
return [k1, v1, k2, v2].join('');
424+
});
425+
426+
element = $compile(
427+
'<ul>' +
428+
'<li ng-repeat="(k1, v1) in [1, 2]">' +
429+
'<div ng-repeat="(k2, v2) in [3, 4] track by trackBy(k1, v1, k2, v2)"></div>' +
430+
'</li>' +
431+
'</ul>')(scope);
432+
scope.$digest();
433+
434+
expect(scope.trackBy).toHaveBeenCalledTimes(4);
435+
expect(scope.trackBy.calls.argsFor(0)).toEqual([0, 1, 0, 3]);
436+
expect(scope.trackBy.calls.argsFor(1)).toEqual([0, 1, 1, 4]);
437+
expect(scope.trackBy.calls.argsFor(2)).toEqual([1, 2, 0, 3]);
438+
expect(scope.trackBy.calls.argsFor(3)).toEqual([1, 2, 1, 4]);
439+
});
403440
});
404441

405442
describe('alias as', function() {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy