diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js
index e82d5e49813a..56e1f7d130d4 100644
--- a/src/ng/directive/ngOptions.js
+++ b/src/ng/directive/ngOptions.js
@@ -428,6 +428,9 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
}
}
+ // The empty option will be compiled and rendered before we first generate the options
+ selectElement.empty();
+
var providedEmptyOption = !!selectCtrl.emptyOption;
var unknownOption = jqLite(optionTemplate.cloneNode(false));
@@ -449,6 +452,9 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
if (!multiple) {
selectCtrl.writeValue = function writeNgOptionsValue(value) {
+ if (!options)
+ return;
+
var selectedOption = options.selectValueMap[selectElement.val()];
var option = options.getOptionFromViewValue(value);
@@ -508,6 +514,9 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
} else {
selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
+ if (!options)
+ return;
+
// Only set `.selected` if necessary, in order to prevent some browsers from
// scrolling to ` ` elements that are outside the `` element's viewport.
@@ -552,13 +561,12 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
if (providedEmptyOption) {
- // we need to remove it before calling selectElement.empty() because otherwise IE will
- // remove the label from the element. wtf?
- selectCtrl.emptyOption.remove();
-
// compile the element since there might be bindings in it
$compile(selectCtrl.emptyOption)(scope);
+ // Ensure that the empty option is always there if it was explicitly provided
+ selectElement.prepend(selectCtrl.emptyOption);
+
if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) {
// This means the empty option has currently no actual DOM node, probably because
// it has been modified by a transclusion directive.
@@ -590,12 +598,6 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
}
- selectElement.empty();
-
- // We need to do this here to ensure that the options object is defined
- // when we first hit it in writeNgOptionsValue
- updateOptions();
-
// We will re-render the option elements if the option values or labels change
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
@@ -624,7 +626,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
// See https://github.com/angular/angular.js/issues/11314 for more info.
// This is unfortunately untestable with unit / e2e tests
- if (option.label !== element.label) {
+ if (isDefined(option.label)) {
element.label = option.label;
element.textContent = option.label;
}
@@ -655,11 +657,6 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
var groupElementMap = {};
- // Ensure that the empty option is always there if it was explicitly provided
- if (providedEmptyOption) {
- selectElement.prepend(selectCtrl.emptyOption);
- }
-
options.items.forEach(function addOption(option) {
var groupElement;
diff --git a/test/ng/directive/ngOptionsSpec.js b/test/ng/directive/ngOptionsSpec.js
index 7a3c4ba492bf..40b56b241e9b 100644
--- a/test/ng/directive/ngOptionsSpec.js
+++ b/test/ng/directive/ngOptionsSpec.js
@@ -779,6 +779,32 @@ describe('ngOptions', function() {
expect(options[2]).not.toBeMarkedAsSelected();
});
+
+ it('should render the initial options only one time', function() {
+ scope.values = ['black', 'white', 'red'];
+ // Insert a select element into the DOM without ng-options to suscribe to DOM change events
+ createSelect({
+ 'ng-model': 'value'
+ });
+
+ // Add ngOptions as attribute before recompiling the select element
+ element.attr('ng-options', 'value for value in values');
+
+ var repaint = 0;
+ // Detect only when the childNodes count changes
+ var lastElementChildrenCount = -1;
+ element[0].addEventListener('DOMSubtreeModified', function(ev) {
+ var childrenCount = jqLite(ev.target).contents().length;
+ if (childrenCount !== lastElementChildrenCount) {
+ repaint++;
+ lastElementChildrenCount = childrenCount;
+ expect(repaint).toBeLessThan(2);
+ }
+ });
+
+ $compile(element)(scope);
+ });
+
describe('disableWhen expression', function() {
describe('on single select', function() {
@@ -2641,6 +2667,25 @@ describe('ngOptions', function() {
dealoc(element);
}));
+
+ it('should remove all comments inside the select element except the initial falsy ngIf empty option', function() {
+ scope.values = [
+ {name:'black'},
+ {name:'white'},
+ {name:'red'}
+ ];
+ scope.isBlank = false;
+
+ createSingleSelect(''
+ + 'blank '
+ + '');
+ scope.$apply('isBlank = true');
+
+ expect(element.find('option')[0].value).toBe('');
+ expect(element.find('option')[0].textContent).toBe('blank');
+ expect(element.find('option')[1].textContent).toBe('black');
+ });
+
});