如何在AngularJS中将指令模板的属性附加到父元素之外的其他元素

How to attach attributes of a directive template to other elements than the parent in AngularJS?

本文关键字:元素 其他 属性 AngularJS 指令      更新时间:2023-09-26

我想实现一个转换<specialInput></specialInput>变成类似的东西

<div id="myDiv">
    <label></label>
    <input></input>
</div>

但是,用户通过将它们放入specialInput-标记(如<specialInput id="test" class="test2" ... required></specialInput>)提供的所有属性当前都附加到div元素myDiv。我希望将所有属性附加到input-标记

注意:一种解决方案是通过将变量(例如type="' + atts.type + '")添加到模板中,手动将每个可能的属性附加到标签标签,然后使用链接函数从父标签中删除这些属性。但这不是我想要实现的。有太多可能的属性需要考虑这个解决方案。

// special-input Angular
(function () {
  'use strict';
  function specialInputDirective($compile) {
    function templateFunction(element, attrs) {
      var template = [
       '<div class="control-group">'+
       '<label for="' + attrs.name + '" class="control-label">' + attrs.label + '</label>' +
       ' <div class="controls">' +
       '  <input id="' + attrs.id + '" type="' + attrs.type + '" ' +                                            
       '       ng-model="' + attrs.name + '" ' +
       (attrs.ngTrueValue ? 'ng-true-value="' + attrs.ngTrueValue + '" ' : '') +
       (attrs.ngFalseValue ? 'ng-false-value="' + attrs.ngFalseValue + '"' : '') +
       ' " placeholder="' + attrs.placeholder + '"' + ' ' +
       attrs.required + ' ' + attrs.disabled + ' ' + attrs.readonly  +
       ' name="' + attrs.name + '"' +
       ' required>' +
       ' </div>' +
       '</div>'
      ].join('');
      return template;
    }
    return {
      link: linkFunction,
      restrict: 'E',
      replace: true,
      transclude: true,
      template: templateFunction
    };
    //////////
    function linkFunction(scope, inputElement, attrs, ctrl, transcludeFn) {
      // Removes user defined attributes from 'control-group'.
      // These are added to the <input> element instead (Defined in: Template)
      // BUT: there are many, many more attributes than this! So this is not the solution I'm aiming for.
      inputElement.removeAttr('id');
      inputElement.removeAttr('type');
      inputElement.removeAttr('label');
      inputElement.removeAttr('placeholder');
      inputElement.removeAttr('required');
      inputElement.removeAttr('disabled');
      inputElement.removeAttr('readonly');
      inputElement.removeAttr('name');
    }
  }
  //////////
  function initModule(angular) {
    ...
  }
}());
<form name="myForm" class="form-horizontal" style="max-width: 700px;">
    <h4>Horizontal Label</h4>
    <special-input name="myName" type="email" label="Email" placeholder="Email"></special-input>
    <special-validation for="myForm.myName" message="required" type="error">This message box represents an error</special-validation>
    <special-validation for="myForm.myName" message="minlength" type="warning">This message box represents a warning</special-validation>
    <special-validation for="myForm.myName" message="maxlength">This message box represents an error</special-validation>
    <special-validation for="myForm.myName" message="email">Invalid email address</special-validation>
    <special-input type="text" label="Text" placeholder="Required input" name="myName2" ng-minlength="5" ng-maxlength="20" required></special-input>
    <special-input type="password" label="Password" placeholder="Password"></special-input>
</form>

您肯定不想在scope中为每个属性创建绑定。相反,解决它的最简单、可能也是最有效的方法是找到一个输入元素,然后在编译函数中将所有属性复制到它。

以下是您指令的简化版本:

.directive('specialInput', function() {
    function linkFunction(scope, element, attrs) {
        // Do something else, bind events, etc ...
    }
    return {
        transclude: true,
        template: function(element, attrs) {
            return '' +
            '<div class="control-group">' +
            '    <label for="' + attrs.name + '">' + attrs.label + '</label>' +
            '    <input>' +
            '</div>';
        },
        compile: function(element, attrs) {
            var input = element.find('input');
            // Copy attrbutes
            angular.forEach(attrs.$attr, function(val, key) {
                input.attr(val, attrs[key]);
            });
            element.replaceWith(element.children()[0]);
            return linkFunction;
        }
    };
});

演示:http://plnkr.co/edit/sQRFzDN0nZJBinu8atOH?p=preview

您必须使用指令的编译功能来实现您想要的功能。

只要你只想将PLAIN属性从父元素移动到内部元素,你基本上就可以了。如果你想将DIRECTIVES属性从父属性移动到子元素,这要复杂得多。

例如,for first选项(只有纯html属性,不移动需要在子元素上编译/链接的内容)是:

/**
 * Input wrapper directive, used to copy all attributes to the underneath input element.
 *
 * To keep a property on the wrapper, prefix it with "wrapper-*"
 * In example, "wrapper-class" will create the "class" property on the wrapper, where "class" will be copied to the input.
 */
module.directive("tlInputWrapper", function($compile) {
    // TODO : keep ngShow, ngIf... and other logic directive on the wrapper.
    return {
        restrict : "A",
        compile  : function($el, $attrs) {
            var input = $el.find("input"); // here, you find the child element you wanna move attributes to
            $attrs.$set("tlInputWrapper", null);
            var attributesToKeep = {};
            _.each($attrs.$attr, function(domAttributeName, normalizedAttributeName) {
                if(domAttributeName.indexOf("wrapper-") === 0) { // I allow keeping attributes on the wrapper. Ex : 'wrapper-class' will stay on the wrapper and be renamed 'class'
                    attributesToKeep[domAttributeName.substring(8)] = $attrs[normalizedAttributeName];
                    $attrs.$set(normalizedAttributeName, null);
                } else {
                    input.attr(domAttributeName, $attrs[normalizedAttributeName]);
                    $attrs.$set(normalizedAttributeName, null);
                }
            });
            _.each(attributesToKeep, function(attrValue, attrName) {
                $attrs.$set(attrName, attrValue);
            });
            return angular.noop;
        }
    };
});