Доступ к scope формы из директивы
http://plnkr.co/edit/M7DNGAbDXQeLRslDIYs5?p=info
Почему свойства элементов контроллера формы не работают в директиве? Как сделать, чтобы Test.pass.$pristine было связано с полем ввода? Нужно именно $pristine элемента, а не всей формы. Задавать имя напрямую в шаблоне директивы нельзя. |
Цитата:
|
Дело в том, что dom содержимое директивы компилится раньше модели в рут элементе, в результате переписывается ссылка на контроллер модели в контроллере формы.
Результат сборки директивы <div name="name" ng-model="model"> <input name="name" ng-model="model"> </div> Код в angular'e v1.2.11, отвечающий за добавление контроллера. /** * @ngdoc function * @name ng.directive:form.FormController#$addControl * @methodOf ng.directive:form.FormController * * @description * Register a control with the form. * * Input elements using ngModelController do this automatically when they are linked. */ form.$addControl = function(control) { // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored // and not added to the scope. Now we throw an error. assertNotHasOwnProperty(control.$name, 'input'); controls.push(control); if (control.$name) { form[control.$name] = control; } }; Сначала добавиться контроллер из инпута, потом его перепишет контроллер из дива. Решение 1. Удалить при компиляции атрибут name http://plnkr.co/edit/NpOoWQFTtOo0ICpXm9kL?p=preview 2. Использовать вместо ng-model, какой либо другой атрибут http://plnkr.co/edit/Yj42bdG0DwWdiShboF3T?p=preview |
Да, совсем забыл, что директива при замене складывает атрибуты исходного элемента с атрибутами элемента шаблона. Получалось, что у нас два элемента с одинаковыми именами.
Если не задавать ng-model, то у заменяемой директивы не появится и ngModelController, соответственно, единственный оставшийся контроллер будет у внутреннего инпута и возьмется его имя. Всё логично! |
Первый способ больше нравится, т.к. пользователю очевиднее задавать модель в ng-model. Проблема в том, что добавляется еще один ngModelController, который участвует в валидации. Поэтому, на родительском диве также формируются классы ng-pristine и т.п., и сообщение об ошибке валидации может дублироваться.
Решил проблему установкой terminal: true, priority: 200 Но не до конца понимаю механизм приоритетов. Правильно ли сделал. Кто-нибудь может объяснить? .directive('formPassword', function() { return { restrict: 'AE', terminal: true, priority: 200, templateUrl: 'template.html', replace: true, scope: { model: '=ngModel' }, compile: function(tElem, tAttrs) { var input = tElem.find('input')[0]; for (var k in tAttrs.$attr) { if (tAttrs.$attr.hasOwnProperty(k)) { if (k !== 'formPassword' && k !== 'type') { input.setAttribute(tAttrs.$attr[k], tAttrs[k]); } if (k !== 'ngModel' && k !== 'class') { tElem.removeAttr(tAttrs.$attr[k]); delete tAttrs.$attr[k]; delete tAttrs[k]; } } } return function(scope, element, attrs, ctrls) { scope.show = false; } } }; }); |
Механизм прост, после сбора всех директив с ноды, angular сортирует их через функцию
/** * Looks for directives on the given node and adds them to the directive collection which is * sorted. * * @param node Node to search. * @param directives An array to which the directives are added to. This array is sorted before * the function returns. * @param attrs The shared attrs object which is used to populate the normalized attributes. * @param {number=} maxPriority Max directive priority. */ function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { ... directives.sort(byPriority); return directives; } /** * Sorting function for bound directives. */ function byPriority(a, b) { var diff = b.priority - a.priority; if (diff !== 0) return diff; if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; return a.index - b.index; } A флаг terminal заставляет оборвать цикл применения директив, тем самым выкидывает все директивы с меньшим приоритетом /** * Once the directives have been collected, their compile functions are executed. This method * is responsible for inlining directive templates as well as terminating the application * of the directives if the terminal directive has been reached. * * @param {Array} directives Array of collected directives to execute their compile function. * this needs to be pre-sorted by priority order. * @param {Node} compileNode The raw DOM node to apply the compile functions to * @param {Object} templateAttrs The shared attribute function * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the * scope argument is auto-generated to the new * child of the transcluded parent scope. * @param {JQLite} jqCollection If we are working on the root of the compile tree then this * argument has the root jqLite array so that we can replace nodes * on it. * @param {Object=} originalReplaceDirective An optional directive that will be ignored when * compiling the transclusion. * @param {Array.<Function>} preLinkFns * @param {Array.<Function>} postLinkFns * @param {Object} previousCompileContext Context used for previous compilation of the current * node * @returns linkFn */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { ... var terminalPriority = -Number.MAX_VALUE, ... // executes all directives on the current element for(var i = 0, ii = directives.length; i < ii; i++) { ... if (terminalPriority > directive.priority) { break; // prevent further processing of directives } ... if (directive.terminal) { nodeLinkFn.terminal = true; terminalPriority = Math.max(terminalPriority, directive.priority); } } ... } |
Спасибо! Вкурил)
|
Часовой пояс GMT +3, время: 18:47. |