ngOptions with "track by" triggers ngChange when ngModel did not change #11936
Description
Overview of the Issue
<select
ng-options="option.id for option in vm.options track by option.id"
ng-model="vm.model"
ng-change="vm.update()">
</select>
vm.options is an array of objects with a unique id property. When the <select>
is first created, ngChange is triggered if ngModel is an object that does not have the same reference as any of the objects in vm.options even though it may be identical in value to one of them. As there has not been a change to the ngModel value, it is not expected that ngChange is triggered.
Motivation for or Use Case
There are two <select>
elements for the user to select the make and model of a car. The options available for <select>
model are dependent on the selected value in <select>
make. Therefore, when the selected value in <select>
make changes, the selected value in <select>
model should be cleared. This works with no issues. However, when navigating away from and back to the <select>
elements, the <select>
model is cleared even though the value of <select>
make did not change. This is not expected.
Angular Version(s)
This issue does not occur with version 1.3.15 through to 1.4.0-beta.6
This issue does occur with versions 1.4.0-rc.0 through to at least 1.4.0-rc.2
Browsers and Operating System
Chrome 43.0.2357.65 m, Firefox 38.0.1, IE 11.0.9600.17801
Windows 8.1 Pro
Reproduce the Error
http://plnkr.co/edit/xe4HPLZVEHqO7XYW1kGX?p=preview
Suggest a Fix
The issue seems to result from this commit: 171b9f7
In the code below, when previousValue !== nextValue
is true, ngModelCtrl.$setViewValue(nextValue)
is invoked, even though ngOptions.trackBy
is true and !equals(previousValue, nextValue)
is false.
// Check to see if the value has changed due to the update to the options
if (!ngModelCtrl.$isEmpty(previousValue)) {
var nextValue = selectCtrl.readValue();
if (ngOptions.trackBy && !equals(previousValue, nextValue) ||
previousValue !== nextValue) {
ngModelCtrl.$setViewValue(nextValue);
ngModelCtrl.$render();
}
}
The code to compare previousValue and nextValue could be changed to:
if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
This would result in a deep comparison when track by is used and a reference comparison when track by is not used.