将指令中的输入包装成角度

时间:2013-12-18 08:33:30

标签: angularjs angularjs-directive angular-ui-bootstrap

我有想法将输入包装到自定义指令中,以确保在我的网站中保持一致的外观和行为。我还想包装bootstrap ui的datepicker和dropdown。此外,该指令应处理验证并显示工具提示。

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>

我尝试了各种方法来实现这一目标,但总是遇到一些权衡:

  1. 使用transclude和replace将输入插入到指令dom结构中(在这种情况下,指令将被限制为'A'而非'E',如上例所示)。这里的问题是,没有简单的方法来访问transcluded元素,因为我想在datepicker的情况下添加自定义属性。我可以使用transclude函数,然后在链接函数中重新编译模板,但这对于此任务来说似乎有点复杂。这也会导致transcluded范围和datepicker的切换状态出现问题(一个在指令范围内,另一个在transcluded范围内)。

  2. 仅使用替换。在这种情况下,所有属性都应用于最外层的div(即使我在编译函数中生成模板dom结构)。如果我只使用输入作为模板,那么属性在输入上,但我需要在链接函数中生成模板然后重新编译它。据我所知,角度的相位模型,我想避免在链接函数中重新编译和更改模板dom(尽管我见过很多人这样做)。

  3. 目前我正在使用第二种方法并在链接功能中生成模板,但我想知道是否有人有更好的想法!

4 个答案:

答案 0 :(得分:8)

以下是我认为正确的方法。和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();
        });
      }
    };
  }
]);

你可以玩here's a plunker

重要scope vs element $ destroy。您必须将清理放在scope.$on("$destroy")而不是element.on("$destroy")(这是我最初尝试的)。如果你在后者(元素)中执行它,那么“ngIf end”注释标记将被泄露。这是由于Angular的ngIf在它做出错误逻辑时如何清理其结束注释标记。通过将指令的清理代码放在范围$ destroy中,您可以将DOM放回原来,就像它在输入之前一样,因此ng-if的清理代码很高兴。在调用element.on(“$ destroy”)时,在ng-if falsey流程中为了解包原始元素而不导致注释标记泄漏,为时已晚。

答案 1 :(得分: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('');
                }
            });            
        }
    }
});

小提琴:http://jsfiddle.net/bZ6WL/

基本上你采用原始输入字段(顺便说一下,also an angularjs directive)并单独构建包装。在这个例子中,我只是手动构建DIV。对于更复杂的东西,你也可以使用一个模板,通过angularjs得到$compile(d)。

使用此类或html属性“wrapForm”的优点:您可以对多种表单输入类型使用相同的指令。

答案 2 :(得分:2)

为什么不将输入包装在编译函数中? 优点是您不必复制属性,也不必在范围销毁功能中进行清理。 请注意,您必须删除指令属性,以防止循环执行。

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;
    }
]);

答案 3 :(得分:0)

基于此:http://angular-tips.com/blog/2014/03/transclusion-and-scopes/

此指令执行转换,但转换的内容使用父作用域,因此所有绑定的工作方式就像转换的内容位于使用包装器的原始作用域中一样。这当然包括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();
                    });
                });
        }
    });