Доступ к 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, время: 02:07. |