Обертывание входных данных в директивы в angular
У меня возникла идея поместить ввод в пользовательские директивы, чтобы гарантировать последовательный внешний вид и поведение на моем сайте. Я также хочу, чтобы упаковать бутстрап ui datepicker и выпадающий список. Кроме того, директива должна обрабатывать подсказки и отображать всплывающие подсказки.
HTML должен выглядеть примерно так:
<my-input required max-length='5' model='text' placeholder='text' name='text'/>
или
<my-datepicker required model='start' placeholder='start' name='start'/>
в директивах я хочу создать структуру dom, например:
<div>
<div>..</div> //display validation in here
<div>..</div> //add button to toggle datepicker (or other stuff) in here
<div>..</div> //add input field in here
</div>
Я пробовал различные способы достижения этого, но всегда сталкивался с некоторыми компромиссами:
-
используя transclude и replace, чтобы вставить вход в структуру dom директив (в этом случае директива будет ограничена "A", а не "E", как в примере выше). Проблема здесь в том, что нет простого способа доступа к элементу transcluded, поскольку я хочу добавить пользовательские атрибуты в случае datepicker. Я мог бы использовать функцию transclude, а затем перекомпилировать шаблон в функции ссылок, но это кажется немного сложным для этой задачи. Это также приводит к проблемам с транскодированной областью и состоянием переключения для datepicker (один из них находится в области директив, другой - в области трансляции).
-
используя только замену. В этом случае все атрибуты применяются к внешнему div (даже если я сгенерирую структуру шаблона dom в функции компиляции). Если я использую только вход в качестве шаблона, то атрибуты находятся на входе, но мне нужно сгенерировать шаблон в функции ссылок, а затем перекомпилировать его. Насколько я понимаю фазовую модель angular, я хотел бы избежать перекомпиляции и изменения шаблона dom в функции ссылки (хотя я видел, как многие это делали).
В настоящее время я работаю со вторым подходом и создаю шаблон в функции ссылок, но мне было интересно, есть ли у кого-то лучшие идеи!
Ответы
Ответ 1
Вот что я считаю правильным способом сделать это. Подобно OP, я хотел иметь возможность использовать директиву атрибута для оболочки input
. Но я также хотел, чтобы он работал с ng-if
и без каких-либо элементов. Как отметил @jantimon, если вы не очистите свои элементы оболочки, они будут задерживаться после ng - если уничтожит исходный элемент.
app.directive("checkboxWrapper", [function() {
return {
restrict: "A",
link: function(scope, element, attrs, ctrl, transclude) {
var wrapper = angular.element('<div class="wrapper">This input is wrappered</div>');
element.after(wrapper);
wrapper.prepend(element);
scope.$on("$destroy", function() {
wrapper.after(element);
wrapper.remove();
});
}
};
}
]);
И здесь плункер, с которым вы можете играть.
ВАЖНО: scope
vs element
$destroy. Вы должны поместить свою очистку в scope.$on("$destroy")
, а не в element.on("$destroy")
(это то, что я изначально пытался). Если вы сделаете это в последнем (элементе), то тег комментария "ngIf end" будет просочиться. Это связано с тем, что Angular ngIf идет об очистке своего тега конца комментария, когда он выполняет свою ложную логику. Поместив ваш код очистки директивы в область $destroy, вы можете поместить DOM обратно, как это было до того, как вы обернули ввод, и поэтому ng-if код очистки будет счастлив. К моменту вызова элемента .on( "$ destroy" ) слишком поздно в потоке ng-if false, чтобы развернуть исходный элемент, не вызывая утечку тега комментария.
Ответ 2
Почему бы не сделать такую директиву?
myApp.directive('wrapForm', function(){
return {
restrict: 'AC',
link: function(scope, inputElement, attributes){
var overallWrap = angular.element('<div />');
var validation = angular.element('<div />').appendTo(overallWrap);
var button = angular.element('<div />').appendTo(overallWrap);
var inputWrap = angular.element('<div />').appendTo(overallWrap);
overallWrap.insertBefore(inputElement);
inputElement.appendTo(inputWrap);
inputElement.on('keyup', function(){
if (inputElement.val()) {
validation.text('Just empty fields are valid!');
} else {
validation.text('');
}
});
}
}
});
Fiddle: http://jsfiddle.net/bZ6WL/
В основном вы берете исходное поле ввода (это, кстати, также директива angularjs) и создавайте обертки отдельно. В этом примере я просто создаю DIVs вручную. Для более сложных вещей вы также можете использовать шаблон, который получает $compile
(d) by angularjs.
Преимущество использования этого класса или атрибута html "wrapForm": вы можете использовать ту же директиву для нескольких типов ввода типов.
Ответ 3
Почему бы не обернуть ввод в функцию компиляции?
Преимущество состоит в том, что вам не придется копировать атрибуты и не придется очищать функцию уничтожения области.
Обратите внимание, что вам нужно удалить атрибут директивы, чтобы предотвратить циклическое выполнение.
(http://jsfiddle.net/oscott9/8er3fu0r/)
angular.module('directives').directive('wrappedWithDiv', [
function() {
var definition = {
restrict: 'A',
compile: function(element, attrs) {
element.removeAttr("wrapped-with-div");
element.replaceWith("<div style='border:2px solid blue'>" +
element[0].outerHTML + "</div>")
}
}
return definition;
}
]);
Ответ 4
Исходя из этого: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
Эта директива делает переключение, но transcluded stuff использует родительскую область, поэтому все привязки работают так, как если бы содержимое, заключенное в transcluded, находилось в исходной области, где используется оболочка. Это, конечно, включает ng-model, также min/max и другие директивы/атрибуты проверки. Должен работать на любой контент. Я не использую директиву ng-transclude, потому что я вручную клонирую элементы и предоставляю им родительскую (контрольную) область. "my-transclude" используется вместо ng-transclude, чтобы указать, куда вставлять транслируемый контент.
Слишком плохо ng-transclude не имеет настройки для контроля области. Это сделало бы все это неудобство ненужным.
И похоже, что они не исправят это: https://github.com/angular/angular.js/issues/5489
controlsModule.directive('myWrapper', function () {
return {
restrict: 'E',
transclude: true,
scope: {
label: '@',
labelClass: '@',
hint: '@'
},
link: link,
template:
'<div class="form-group" title="{{hint}}"> \
<label class="{{labelClass}} control-label">{{label}}</label> \
<my-transclude></my-transclude> \
</div>'
};
function link(scope, iElement, iAttrs, ctrl, transclude) {
transclude(scope.$parent,
function (clone, scope) {
iElement.find("my-transclude").replaceWith(clone);
scope.$on("$destroy", function () {
clone.remove();
});
});
}
});