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

Commit 41671e2

Browse files
committed
fix(select): fix ng-change over firing when using trackBy
closes #4118
1 parent 27c65fd commit 41671e2

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

src/components/select/select.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
332332
syncLabelText();
333333
};
334334
selectMenuCtrl.refreshViewValue();
335-
ngModelCtrl.$render();
336335
}
337336
});
338337
});
@@ -430,7 +429,6 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
430429
}
431430
selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value);
432431
selectMenuCtrl.refreshViewValue();
433-
ngModelCtrl.$render();
434432
}
435433
}
436434
}
@@ -688,8 +686,15 @@ function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
688686
values.push(self.selected[hashKey]);
689687
}
690688
}
691-
self.ngModel.$setViewValue(self.isMultiple ? values : values[0]);
692-
self.ngModel.$render();
689+
var usingTrackBy = self.ngModel.$options && self.ngModel.$options.trackBy;
690+
691+
var newVal = self.isMultiple ? values : values[0];
692+
var prevVal = self.ngModel.$modelValue;
693+
694+
if (usingTrackBy ? !angular.equals(prevVal, newVal) : prevVal != newVal) {
695+
self.ngModel.$setViewValue(newVal);
696+
self.ngModel.$render();
697+
}
693698
};
694699

695700
function renderMultiple() {
@@ -770,7 +775,6 @@ function OptionDirective($mdButtonInkRipple, $mdUtil) {
770775
selectCtrl.deselect(optionCtrl.hashKey);
771776
}
772777
selectCtrl.refreshViewValue();
773-
selectCtrl.ngModel.$render();
774778
});
775779
});
776780

src/components/select/select.spec.js

+61-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ describe('<md-select>', function() {
1212

1313
var selectMenus = $document.find('md-select-menu');
1414
selectMenus.remove();
15+
16+
var backdrops = $document.find('md-backdrop');
17+
backdrops.remove();
1518
}));
1619

1720
it('should preserve tabindex', inject(function($document) {
@@ -40,7 +43,7 @@ describe('<md-select>', function() {
4043
expect(container.classList.contains('test')).toBe(true);
4144
}));
4245

43-
it('closes the menu if the element on backdrop click', inject(function($document, $rootScope) {
46+
it('calls md-on-close when the select menu closes', inject(function($document, $rootScope) {
4447
var called = false;
4548
$rootScope.onClose = function() {
4649
called = true;
@@ -49,16 +52,44 @@ describe('<md-select>', function() {
4952
openSelect(select);
5053

5154
// Simulate click bubble from option to select menu handler
52-
select.triggerHandler({
53-
type: 'click',
54-
target: angular.element($document.find('md-option')[0])
55-
});
55+
clickOption(0);
5656

5757
waitForSelectClose();
5858

5959
expect(called).toBe(true);
6060
}));
6161

62+
it('closes on backdrop click', inject(function($document) {
63+
var select = setupSelect('ng-model="val"', [1, 2, 3]).find('md-select');
64+
openSelect(select);
65+
66+
// Simulate click bubble from option to select menu handler
67+
var backdrop = $document.find('md-backdrop');
68+
expect(backdrop.length).toBe(1);
69+
backdrop.triggerHandler('click');
70+
71+
waitForSelectClose();
72+
73+
backdrop = $document.find('md-backdrop');
74+
expect(backdrop.length).toBe(0);
75+
}));
76+
77+
it('should not trigger ng-change without a change when using trackBy', inject(function($rootScope) {
78+
var changed = false;
79+
$rootScope.onChange = function() { changed = true; };
80+
$rootScope.val = { id: 1, name: 'Bob' };
81+
82+
var opts = [ { id: 1, name: 'Bob' }, { id: 2, name: 'Alice' } ];
83+
var select = setupSelect('ng-model="$root.val" ng-change="onChange()" ng-model-options="{trackBy: \'$value.id\'}"', opts);
84+
expect(changed).toBe(false);
85+
86+
openSelect(select);
87+
clickOption(1);
88+
waitForSelectClose();
89+
expect($rootScope.val.id).toBe(2);
90+
expect(changed).toBe(true);
91+
}));
92+
6293
it('should set touched only after closing', inject(function($compile, $rootScope) {
6394
var form = $compile('<form name="myForm">' +
6495
'<md-select name="select" ng-model="val">' +
@@ -825,15 +856,20 @@ describe('<md-select>', function() {
825856
}
826857

827858
function openSelect(el) {
859+
if (el[0].nodeName != 'MD-SELECT') {
860+
el = el.find('md-select');
861+
}
828862
try {
829863
el.triggerHandler('click');
830864
waitForSelectOpen();
831865
el.triggerHandler('blur');
832-
} catch(e) { }
866+
} catch (e) { }
833867
}
834868

835869
function closeSelect() {
836870
inject(function($document) {
871+
var backdrop = $document.find('md-backdrop');
872+
if (!backdrop.length) throw Error('Attempted to close select with no backdrop present');
837873
$document.find('md-backdrop').triggerHandler('click');
838874
});
839875
waitForSelectClose();
@@ -859,4 +895,23 @@ describe('<md-select>', function() {
859895
});
860896
}
861897

898+
function clickOption(index) {
899+
inject(function($rootScope, $document) {
900+
var openMenu = $document.find('md-select-menu');
901+
var opt = $document.find('md-option')[index];
902+
903+
if (!openMenu.length) throw Error('No select menu currently open');
904+
if (!opt) throw Error('Could not find option at index: ' + index);
905+
var target = angular.element(opt);
906+
angular.element(openMenu).triggerHandler({
907+
type: 'click',
908+
target: target
909+
});
910+
angular.element(openMenu).triggerHandler({
911+
type: 'mouseup',
912+
currentTarget: openMenu[0]
913+
});
914+
});
915+
}
916+
862917
});

0 commit comments

Comments
 (0)