From d173dbbc0e8d0b2889312cd4e2f843d5bab61682 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Thu, 1 Dec 2016 12:04:19 +0100 Subject: [PATCH] fix(ngOptions): don't add comment nodes as empty options When the "empty option" element contains a transclusion directive, the result of the compilation always includes a comment node. Since we are adding / removing the "selected" attribute on the empty option, we need to make sure it's an actual element. To solve this, we take advantage of the fact the each option element has an option directive that tries to register the option with the selectController. With ngOptions, this registerOption function is normally noop'd since it's not possible to add dynamic options. Now if the result of the empty option compilation is a comment, we re-define the function so that it catches empty options when they are actually linked / rendered. Related #15454 Closes #15459 --- src/ng/directive/ngOptions.js | 33 ++++++++++- src/ng/directive/select.js | 19 ++++--- test/ng/directive/ngOptionsSpec.js | 90 ++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index 8d562082f983..7265a1f6e892 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -420,6 +420,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, // option when the viewValue does not match any of the option values. for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { if (children[i].value === '') { + selectCtrl.hasEmptyOption = true; selectCtrl.emptyOption = children.eq(i); break; } @@ -556,9 +557,35 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, // compile the element since there might be bindings in it $compile(selectCtrl.emptyOption)(scope); - // remove the class, which is added automatically because we recompile the element and it - // becomes the compilation root - selectCtrl.emptyOption.removeClass('ng-scope'); + 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. + selectCtrl.hasEmptyOption = 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() === '') { + selectCtrl.hasEmptyOption = true; + selectCtrl.emptyOption = optionEl; + selectCtrl.emptyOption.removeClass('ng-scope'); + // This ensures the new empty option is selected if previously no option was selected + ngModelCtrl.$render(); + + optionEl.on('$destroy', function() { + selectCtrl.hasEmptyOption = false; + selectCtrl.emptyOption = undefined; + }); + } + }; + + } else { + // remove the class, which is added automatically because we recompile the element and it + // becomes the compilation root + selectCtrl.emptyOption.removeClass('ng-scope'); + } + } selectElement.empty(); diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index 34f7c8a2a853..20271e2077aa 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -32,6 +32,14 @@ var SelectController = // to create it in