From c547ca45ec07f5b2f83e7c8507d79db051780ce5 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Mon, 28 Sep 2015 23:39:21 +0200 Subject: [PATCH] fix(ngOptions): override select option registration When ngOptions is present on a select, the option directive should not be able to register options on the selectCtrl since this may cause errors during the ngOptions lifecycle. This can happen in the following cases: - there is a blank option below the select element, an ngModel directive, an ngOptions directive and some other directive on the select element, which compiles the children of the select (i.e. the option elements) before ngOptions is has finished linking. - there is a blank option below the select element, an ngModel directive, an ngOptions directive and another directive, which uses templateUrl and replace:true. What happens is: - the option directive is compiled and adds an element `$destroy` listener that will call `ngModel.$render` when the option element is removed. - when `ngOptions` processes the option, it removes the element, and triggers the `$destroy` listener on the option. - the registered `$destroy` listener calls `$render` on `ngModel`. - $render calls `selectCtrl.writeValue()`, which accesses the `options` object in the `ngOptions` directive. - Since `ngOptions` has not yet completed linking the `options` has not yet been defined and we get an error. This fix moves the registration code for the `option` directive into the `SelectController`, which can then be easily overridden by the `ngOptions` directive as a `noop`. Fixes #11685 Closes #12972 Closes #12968 Closes #13012 --- src/ng/directive/ngOptions.js | 20 ++++-- src/ng/directive/select.js | 109 +++++++++++++++-------------- test/ng/directive/ngOptionsSpec.js | 78 ++++++++++++++++++++- 3 files changed, 147 insertions(+), 60 deletions(-) diff --git a/src/ng/directive/ngOptions.js b/src/ng/directive/ngOptions.js index 680472916359..f341d08a6267 100644 --- a/src/ng/directive/ngOptions.js +++ b/src/ng/directive/ngOptions.js @@ -392,11 +392,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { var optionTemplate = document.createElement('option'), optGroupTemplate = document.createElement('optgroup'); - return { - restrict: 'A', - terminal: true, - require: ['select', 'ngModel'], - link: function(scope, selectElement, attr, ctrls) { + function ngOptionsPostLink(scope, selectElement, attr, ctrls) { var selectCtrl = ctrls[0]; var ngModelCtrl = ctrls[1]; @@ -448,7 +444,6 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { unknownOption.remove(); }; - // Update the controller methods for multiple selectable options if (!multiple) { @@ -726,7 +721,20 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { } } + } + return { + restrict: 'A', + terminal: true, + require: ['select', 'ngModel'], + link: { + pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { + // Deactivate the SelectController.register method to prevent + // option directives from accidentally registering themselves + // (and unwanted $destroy handlers etc.) + ctrls[0].register = noop; + }, + post: ngOptionsPostLink } }; }]; diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index df2bcee32437..111930dc23dd 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -2,6 +2,15 @@ var noopNgModelController = { $setViewValue: noop, $render: noop }; +function chromeHack(optionElement) { + // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 + // Adding an