перенос входных данных в директивы в angular

У меня возникла идея обернуть входные данные в пользовательские директивы, чтобы гарантировать единообразный внешний вид и поведение на моем сайте. Я также хочу обернуть средство выбора даты и раскрывающийся список bootstrap ui. Кроме того, директива должна обрабатывать валидацию и отображать всплывающие подсказки.

HTML-код должен выглядеть примерно так:

<my-input required max-length='5' model='text' placeholder='text' name='text'/>

or

<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>

Я пробовал разные способы добиться этого, но всегда сталкивался с некоторыми компромиссами:

  1. используя transclude и replace для вставки входных данных в структуру dom директив (в этом случае директива будет ограничена «A», а не «E», как в примере выше). Проблема здесь в том, что нет простого способа получить доступ к включенному элементу, поскольку я хочу добавить настраиваемые атрибуты в случае средства выбора даты. Я мог бы использовать функцию преобразования, а затем перекомпилировать шаблон в функции ссылки, но это кажется немного сложным для этой задачи. Это также приводит к проблемам с включенной областью действия и состоянием переключения для средства выбора даты (один находится в области директив, другой — во включенной области).

  2. используя только замену. В этом случае все атрибуты применяются к самому внешнему div (даже если я генерирую структуру шаблона dom в функции компиляции). Если я использую только ввод в качестве шаблона, то атрибуты будут на входе, но мне нужно сгенерировать шаблон в функции ссылки, а затем перекомпилировать его. Насколько я понимаю фазовую модель angular, хотелось бы избежать перекомпиляции и изменения шаблона dom в функции ссылки (хотя я видел, как многие так делают).

В настоящее время я работаю со вторым подходом и генерирую шаблон в функции ссылки, но мне было интересно, есть ли у кого-нибудь идеи получше!


person roemer    schedule 18.12.2013    source источник
comment
Какие настраиваемые атрибуты вы хотите добавить? Я думаю, что полный пример HTML, в который вы хотите, чтобы директива отображалась (пользовательские атрибуты и все такое), был бы полезен здесь.   -  person S McCrohan    schedule 19.12.2013
comment
В случае средства выбора даты я хотел бы установить стандартные значения для всего приложения. Результирующий тег ввода html должен выглядеть так: <input type="text" data-datepicker-popup="" data-is-open="opened" data-current-text="{{current}}" data-close-text="{{text}}" data-clear-text="{{clear}}" data-show-weeks="{{showweeks}}" data-starting-day="{{startingday}}" placeholder="{{placeholder}}" data-ng-model="start" name="start" required="required" class="ng-isolate-scope ng-pristine ng-valid ng-valid-required">   -  person roemer    schedule 19.12.2013


Ответы (4)


Вот что я считаю правильным способом сделать это. Как и в случае с OP, я хотел иметь возможность использовать директиву атрибута для обертки input. Но я также хотел, чтобы он работал с ng-if и т. д. без утечки каких-либо элементов. Как указал @jantimon, если вы не очистите свои элементы-оболочки, они останутся после того, как ng-if уничтожит исходный элемент.

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» будет просочиться. Это связано с тем, как ngIf Angular очищает свой тег конечного комментария, когда выполняет свою ложную логику. Поместив код очистки вашей директивы в область $destroy, вы можете вернуть DOM, как это было до того, как вы обернули ввод, и поэтому код очистки ng-if доволен. К моменту вызова element.on("$destroy") в потоке ng-if falsey уже слишком поздно разворачивать исходный элемент, не вызывая утечки тега комментария.

person BrandonLWhite    schedule 22.12.2014
comment
jqLite Angular имеет wrap. Почему бы не использовать это вместо after и prepend? - person TheSharpieOne; 18.06.2015
comment
Попытка wrap вызвала у меня некоторые трудности. Наверняка синтаксис был бы чище и компактнее, но по какой-то причине очистка не работает при использовании wrap. Попробуйте wrap в Plunker, затем установите флажок Show (переключает условие ngIf). Я признаю, что я не исследовал это дальше, но меня интересуют подробности, если кто-то захочет разобраться в этом подробнее. - person BrandonLWhite; 16.07.2015

Почему бы не сделать такую ​​директиву?

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('');
                }
            });            
        }
    }
});

Скрипт: http://jsfiddle.net/bZ6WL/

По сути, вы берете исходное поле ввода (которое, кстати, также является директивой angularjs) и создайте оболочки отдельно. В этом примере я просто создаю DIV вручную. Для более сложных вещей вы также можете использовать шаблон, который получает $compile(d) с помощью angularjs.

Преимущество использования этого класса или html-атрибута «wrapForm»: вы можете использовать одну и ту же директиву для нескольких типов ввода формы.

person Armin    schedule 23.12.2013
comment
Я видел это в скрипке, но это не работает для меня, не знаю почему. У меня есть jquery-1.10.2 и angular.js в моем приложении, и все равно это не работает!! - person Mital Pritmani; 15.04.2014
comment
Осторожно - если вы используете это с ng-if, это не очистит обертки! - person jantimon; 09.09.2014
comment
Это хорошее предложение, но в angular нет appendTo или insertBefore. Вместо этого можно использовать Append и after (соответственно) для достижения аналогичного результата. - person Theo; 30.12.2014

Почему бы не обернуть ввод в функцию компиляции? Преимущество заключается в том, что вам не придется копировать атрибуты и не придется выполнять очистку в функции уничтожения области видимости. Обратите внимание, что вы должны удалить атрибут директивы, чтобы предотвратить циклическое выполнение.

(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;
    }
]);
person Orri Scott    schedule 10.02.2016

На основании этого: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/

Эта директива выполняет включение, но включенный материал использует родительскую область, поэтому все привязки работают так, как если бы включенный контент находился в исходной области, в которой используется оболочка. Это, конечно, включает ng-модель, а также 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();
                    });
                });
        }
    });
person Zar Shardan    schedule 09.09.2016