diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index 886aae20306a..91585934445d 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -405,21 +405,35 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { value = valueFn(scope, locals); } } - // Update the null option's selected property here so $render cleans it up correctly - if (optionGroupsCache[0].length > 1) { - if (optionGroupsCache[0][1].id !== key) { - optionGroupsCache[0][1].selected = false; - } - } } ctrl.$setViewValue(value); + render(); }); }); ctrl.$render = render; - // TODO(vojta): can't we optimize this ? - scope.$watch(render); + scope.$watch(valuesFn, render, true); + scope.$watch(getSelectedSet, render, true); + + function getSelectedSet() { + var selectedSet = false; + if (multiple) { + var modelValue = ctrl.$modelValue; + if (trackFn && isArray(modelValue)) { + selectedSet = new HashMap([]); + var locals = {}; + for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { + locals[valueName] = modelValue[trackIndex]; + selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); + } + } else { + selectedSet = new HashMap(modelValue); + } + } + return selectedSet; + } + function render() { // Temporary location for the option groups before we render them @@ -437,22 +451,11 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { groupIndex, index, locals = {}, selected, - selectedSet = false, // nothing is selected yet + selectedSet = getSelectedSet(), lastElement, element, label; - if (multiple) { - if (trackFn && isArray(modelValue)) { - selectedSet = new HashMap([]); - for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { - locals[valueName] = modelValue[trackIndex]; - selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); - } - } else { - selectedSet = new HashMap(modelValue); - } - } // We now build up the list of options we need (we merge later) for (index = 0; length = keys.length, index < length; index++) { @@ -548,7 +551,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { lastElement.val(existingOption.id = option.id); } // lastElement.prop('selected') provided by jQuery has side-effects - if (existingOption.selected !== option.selected) { + if (lastElement[0].selected !== option.selected) { lastElement.prop('selected', (existingOption.selected = option.selected)); if (msie) { // See #7692 diff --git a/test/ng/directive/selectSpec.js b/test/ng/directive/selectSpec.js index 9ce01704c136..3261605ffbde 100644 --- a/test/ng/directive/selectSpec.js +++ b/test/ng/directive/selectSpec.js @@ -735,6 +735,8 @@ describe('select', function() { it('should not update selected property of an option element on digest with no change event', function() { + // ng-options="value.name for value in values" + // ng-model="selected" createSingleSelect(); scope.$apply(function() { @@ -743,6 +745,11 @@ describe('select', function() { }); var options = element.find('option'); + + expect(scope.selected).toEqual({ name: 'A' }); + expect(options.eq(0).prop('selected')).toBe(true); + expect(options.eq(1).prop('selected')).toBe(false); + var optionToSelect = options.eq(1); expect(optionToSelect.text()).toBe('B'); @@ -1148,8 +1155,73 @@ describe('select', function() { browserTrigger(element, 'change'); expect(scope.selected).toEqual(null); }); + + + // Regression https://github.com/angular/angular.js/issues/7855 + it('should update the model with ng-change', function() { + createSelect({ + 'ng-change':'change()', + 'ng-model':'selected', + 'ng-options':'value for value in values' + }); + + scope.$apply(function() { + scope.values = ['A', 'B']; + scope.selected = 'A'; + }); + + scope.change = function() { + scope.selected = 'A'; + }; + + element.find('option')[1].selected = true; + + browserTrigger(element, 'change'); + expect(element.find('option')[0].selected).toBeTruthy(); + expect(scope.selected).toEqual('A'); + }); }); + describe('disabled blank', function() { + it('should select disabled blank by default', function() { + var html = ''; + scope.$apply(function() { + scope.choices = ['A', 'B', 'C']; + }); + + compile(html); + + var options = element.find('option'); + var optionToSelect = options.eq(0); + expect(optionToSelect.text()).toBe('Choose One'); + expect(optionToSelect.prop('selected')).toBe(true); + expect(element[0].value).toBe(''); + + dealoc(element); + }); + + + it('should select disabled blank by default when select is required', function() { + var html = ''; + scope.$apply(function() { + scope.choices = ['A', 'B', 'C']; + }); + + compile(html); + + var options = element.find('option'); + var optionToSelect = options.eq(0); + expect(optionToSelect.text()).toBe('Choose One'); + expect(optionToSelect.prop('selected')).toBe(true); + expect(element[0].value).toBe(''); + + dealoc(element); + }); + }); describe('select-many', function() { @@ -1197,6 +1269,7 @@ describe('select', function() { expect(scope.selected).toEqual([scope.values[0]]); }); + it('should select from object', function() { createSelect({ 'ng-model':'selected',