diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index a8adfb0b2e1b..ad199406d694 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -429,6 +429,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, } var providedEmptyOption = !!emptyOption; + var emptyOptionRendered = false; var unknownOption = jqLite(optionTemplate.cloneNode(false)); unknownOption.val('?'); @@ -445,14 +446,16 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, selectElement.prepend(emptyOption); } selectElement.val(''); - emptyOption.prop('selected', true); // needed for IE - emptyOption.attr('selected', true); + if (emptyOptionRendered) { + emptyOption.prop('selected', true); // needed for IE + emptyOption.attr('selected', true); + } }; var removeEmptyOption = function() { if (!providedEmptyOption) { emptyOption.remove(); - } else { + } else if (emptyOptionRendered) { emptyOption.removeAttr('selected'); } }; @@ -587,9 +590,35 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, // compile the element since there might be bindings in it $compile(emptyOption)(scope); - // remove the class, which is added automatically because we recompile the element and it - // becomes the compilation root - emptyOption.removeClass('ng-scope'); + if (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. + + emptyOptionRendered = false; + + // Redefine the registerOption function, which will catch + // options that are added by ngIf etc. (rendering of the node is async because of + // lazy transclusion) + selectCtrl.registerOption = function(optionScope, optionEl) { + if (optionEl.val() === '') { + emptyOptionRendered = true; + emptyOption = optionEl; + emptyOption.removeClass('ng-scope'); + // This ensures the new empty option is selected if previously no option was selected + ngModelCtrl.$render(); + + optionEl.on('$destroy', function() { + emptyOption = undefined; + emptyOptionRendered = false; + }); + } + }; + + } else { + emptyOption.removeClass('ng-scope'); + emptyOptionRendered = true; + } + } else { emptyOption = jqLite(optionTemplate.cloneNode(false)); } diff --git a/test/ng/directive/ngOptionsSpec.js b/test/ng/directive/ngOptionsSpec.js index edfc0f98d108..f1f752bf352d 100644 --- a/test/ng/directive/ngOptionsSpec.js +++ b/test/ng/directive/ngOptionsSpec.js @@ -2558,6 +2558,95 @@ describe('ngOptions', function() { }); + it('should select the correct option after linking when the ngIf expression is initially falsy', function() { + scope.values = [ + {name:'black'}, + {name:'white'}, + {name:'red'} + ]; + scope.selected = scope.values[2]; + + expect(function() { + createSingleSelect(''); + scope.$apply(); + }).not.toThrow(); + + expect(element.find('option')[2]).toBeMarkedAsSelected(); + expect(linkLog).toEqual(['linkNgOptions']); + }); + + + it('should add / remove the "selected" attribute on empty option which has an initially falsy ngIf expression', function() { + scope.values = [ + {name:'black'}, + {name:'white'}, + {name:'red'} + ]; + scope.selected = scope.values[2]; + + createSingleSelect(''); + scope.$apply(); + + expect(element.find('option')[2]).toBeMarkedAsSelected(); + + scope.$apply('isBlank = true'); + expect(element.find('option')[0].value).toBe(''); + expect(element.find('option')[0]).not.toBeMarkedAsSelected(); + + scope.$apply('selected = null'); + expect(element.find('option')[0].value).toBe(''); + expect(element.find('option')[0]).toBeMarkedAsSelected(); + + scope.selected = scope.values[1]; + scope.$apply(); + expect(element.find('option')[0].value).toBe(''); + expect(element.find('option')[0]).not.toBeMarkedAsSelected(); + expect(element.find('option')[2]).toBeMarkedAsSelected(); + }); + + + it('should add / remove the "selected" attribute on empty option which has an initially truthy ngIf expression when no option is selected', function() { + scope.values = [ + {name:'black'}, + {name:'white'}, + {name:'red'} + ]; + scope.isBlank = true; + + createSingleSelect(''); + scope.$apply(); + + expect(element.find('option')[0].value).toBe(''); + expect(element.find('option')[0]).toBeMarkedAsSelected(); + + scope.selected = scope.values[2]; + scope.$apply(); + expect(element.find('option')[0]).not.toBeMarkedAsSelected(); + expect(element.find('option')[3]).toBeMarkedAsSelected(); + }); + + + it('should add the "selected" attribute on empty option which has an initially falsy ngIf expression when no option is selected', function() { + scope.values = [ + {name:'black'}, + {name:'white'}, + {name:'red'} + ]; + + createSingleSelect(''); + scope.$apply(); + + expect(element.find('option')[0]).not.toBeMarkedAsSelected(); + + scope.isBlank = true; + scope.$apply(); + + expect(element.find('option')[0].value).toBe(''); + expect(element.find('option')[0]).toBeMarkedAsSelected(); + expect(element.find('option')[1]).not.toBeMarkedAsSelected(); + }); + + it('should not throw with a directive that replaces', inject(function($templateCache, $httpBackend) { $templateCache.put('select_template.html', '');