简体   繁体   English

从AngularJS中的指令添加指令

[英]Add directives from directive in AngularJS

I'm trying to build a directive that takes care of adding more directives to the element it is declared on. 我正在尝试构建一个指令,该指令负责在其声明的元素上添加更多指令 For example, I want to build a directive that takes care of adding datepicker , datepicker-language and ng-required="true" . 例如,我要构建一个指令,该指令负责添加datepickerdatepicker-languageng-required="true"

If I try to add those attributes and then use $compile I obviously generate an infinite loop, so I am checking if I have already added the needed attributes: 如果我尝试添加这些属性,然后使用$compile ,显然会生成一个无限循环,因此我正在检查是否已添加所需的属性:

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        element.attr('datepicker', 'someValue');
        element.attr('datepicker-language', 'en');
        // some more
        $compile(element)(scope);
      }
    };
  });

Of course, if I don't $compile the element, the attributes will be set but the directive won't be bootstrapped. 当然,如果我不$compile该元素,则将设置属性,但不会引导该指令。

Is this approach correct or am I doing it wrong? 这种方法正确还是我做错了? Is there a better way to achieve the same behavior? 有没有更好的方法来实现相同的行为?

UDPATE : given the fact that $compile is the only way to achieve this, is there a way to skip the first compilation pass (the element may contain several children)? UDPATE :鉴于$compile是实现此目标的唯一方法,是否有一种方法可以跳过第一个编译过程(该元素可能包含多个子级)? Maybe by setting terminal:true ? 也许通过设置terminal:true

UPDATE 2 : I have tried putting the directive into a select element and, as expected, the compilation runs twice, which means there is twice the number of expected option s. 更新2 :我尝试将指令放入select元素中,并且按预期方式,编译运行两次,这意味着预期option s的数量是两倍。

In cases where you have multiple directives on a single DOM element and where the order in which they're applied matters, you can use the priority property to order their application. 如果在单个DOM元素上具有多个指令,并且应用顺序很重要,则可以使用priority属性对应用程序进行排序。 Higher numbers run first. 较高的数字优先。 The default priority is 0 if you don't specify one. 如果未指定,则默认优先级为0。

EDIT : after the discussion, here's the complete working solution. 编辑 :讨论之后,这是完整的工作解决方案。 The key was to remove the attribute : element.removeAttr("common-things"); 关键是删除以下属性element.removeAttr("common-things"); , and also element.removeAttr("data-common-things"); ,以及element.removeAttr("data-common-things"); (in case users specify data-common-things in the html) (以防用户在html中指定data-common-things

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false, 
      terminal: true, //this setting is important, see explanation below
      priority: 1000, //this setting is important, see explanation below
      compile: function compile(element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        return {
          pre: function preLink(scope, iElement, iAttrs, controller) {  },
          post: function postLink(scope, iElement, iAttrs, controller) {  
            $compile(iElement)(scope);
          }
        };
      }
    };
  });

Working plunker is available at: http://plnkr.co/edit/Q13bUt?p=preview 可以使用工作的监听器: http ://plnkr.co/edit/Q13bUt?p=preview

Or: 要么:

angular.module('app')
  .directive('commonThings', function ($compile) {
    return {
      restrict: 'A',
      replace: false,
      terminal: true,
      priority: 1000,
      link: function link(scope,element, attrs) {
        element.attr('tooltip', '{{dt()}}');
        element.attr('tooltip-placement', 'bottom');
        element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
        element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html

        $compile(element)(scope);
      }
    };
  });

DEMO 演示

Explanation why we have to set terminal: true and priority: 1000 (a high number): 解释为什么我们必须设置terminal: truepriority: 1000 (高数字):

When the DOM is ready, angular walks the DOM to identify all registered directives and compile the directives one by one based on priority if these directives are on the same element . 当DOM准备就绪时, 如果这些指令位于同一元素上 ,则angular遍历DOM以标识所有已注册的指令,并根据priority逐一编译指令。 We set our custom directive's priority to a high number to ensure that it will be compiled first and with terminal: true , the other directives will be skipped after this directive is compiled. 我们设定的自定义指令的优先级为较高的数字,以确保将第一和编译terminal: true ,该指令被编译后的其他指令将被跳过

When our custom directive is compiled, it will modify the element by adding directives and removing itself and use $compile service to compile all the directives (including those that were skipped) . 编译我们的自定义指令时,它将通过添加指令并删除自身来修改元素,并使用$ compile服务编译所有指令(包括已跳过的指令)

If we don't set terminal:true and priority: 1000 , there is a chance that some directives are compiled before our custom directive. 如果我们没有设置terminal:truepriority: 1000 ,那么某些指令可能会自定义指令之前被编译。 And when our custom directive uses $compile to compile the element => compile again the already compiled directives. 当我们的自定义指令使用$ compile编译element =>时,再次编译已经编译的指令。 This will cause unpredictable behavior especially if the directives compiled before our custom directive have already transformed the DOM. 这将导致无法预测的行为,尤其是如果在我们的自定义指令之前编译的指令已经转换了DOM。

For more information about priority and terminal, check out How to understand the `terminal` of directive? 有关优先级和终端的更多信息,请查看如何理解指令的“终端”?

An example of a directive that also modifies the template is ng-repeat (priority = 1000), when ng-repeat is compiled, ng-repeat make copies of the template element before other directives get applied . 一个指令,它还会修改模板的一个例子是ng-repeat (优先级= 1000),当ng-repeat编译, ng-repeat 得到应用其他指令前的模板元素进行复印

Thanks to @Izhaki's comment, here is the reference to ngRepeat source code: https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js 感谢@Izhaki的评论,这里是对ngRepeat源代码的引用: https : //github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

You can actually handle all of this with just a simple template tag. 实际上,您只需一个简单的模板标签就可以处理所有这些问题。 See http://jsfiddle.net/m4ve9/ for an example. 有关示例,请参见http://jsfiddle.net/m4ve9/ Note that I actually didn't need a compile or link property on the super-directive definition. 注意,实际上我不需要超级指令定义上的compile或link属性。

During the compilation process, Angular pulls in the template values before compiling, so you can attach any further directives there and Angular will take care of it for you. 在编译过程中,Angular在编译之前会提取模板值,因此您可以在此处附加任何其他指令,Angular会为您处理。

If this is a super directive that needs to preserve the original internal content, you can use transclude : true and replace the inside with <ng-transclude></ng-transclude> 如果这是需要保留原始内部内容的超级指令,则可以使用transclude : true并将内部内容替换为<ng-transclude></ng-transclude>

Hope that helps, let me know if anything is unclear 希望能有所帮助,让我知道是否有任何不清楚的地方

Alex 亚历克斯

Here's a solution that moves the directives that need to be added dynamically, into the view and also adds some optional (basic) conditional-logic. 这是一个解决方案,它将需要动态添加的指令移动到视图中,并且还添加了一些可选的(基本)条件逻辑。 This keeps the directive clean with no hard-coded logic. 这使指令保持干净,没有硬编码逻辑。

The directive takes an array of objects, each object contains the name of the directive to be added and the value to pass to it (if any). 该指令采用一个对象数组,每个对象都包含要添加的指令的名称和要传递给它的值(如果有)。

I was struggling to think of a use-case for a directive like this until I thought that it might be useful to add some conditional logic that only adds a directive based on some condition (though the answer below is still contrived). 我一直在努力思考这样一个指令的用例,直到我认为添加一些仅基于某种条件添加指令的条件逻辑可能很有用(尽管下面的答案仍然是故意的)。 I added an optional if property that should contain a bool value, expression or function (eg defined in your controller) that determines if the directive should be added or not. 我添加了一个可选的if属性,该属性应包含布尔值,表达式或函数(例如,在控制器中定义),该布尔值,表达式或函数确定是否应添加指令。

I'm also using attrs.$attr.dynamicDirectives to get the exact attribute declaration used to add the directive (eg data-dynamic-directive , dynamic-directive ) without hard-coding string values to check for. 我还使用attrs.$attr.dynamicDirectives来获取用于添加指令的确切属性声明(例如data-dynamic-directivedynamic-directive ),而无需对字符串值进行硬编码检查。

Plunker Demo 柱塞演示

 angular.module('plunker', ['ui.bootstrap']) .controller('DatepickerDemoCtrl', ['$scope', function($scope) { $scope.dt = function() { return new Date(); }; $scope.selects = [1, 2, 3, 4]; $scope.el = 2; // For use with our dynamic-directive $scope.selectIsRequired = true; $scope.addTooltip = function() { return true; }; } ]) .directive('dynamicDirectives', ['$compile', function($compile) { var addDirectiveToElement = function(scope, element, dir) { var propName; if (dir.if) { propName = Object.keys(dir)[1]; var addDirective = scope.$eval(dir.if); if (addDirective) { element.attr(propName, dir[propName]); } } else { // No condition, just add directive propName = Object.keys(dir)[0]; element.attr(propName, dir[propName]); } }; var linker = function(scope, element, attrs) { var directives = scope.$eval(attrs.dynamicDirectives); if (!directives || !angular.isArray(directives)) { return $compile(element)(scope); } // Add all directives in the array angular.forEach(directives, function(dir){ addDirectiveToElement(scope, element, dir); }); // Remove attribute used to add this directive element.removeAttr(attrs.$attr.dynamicDirectives); // Compile element to run other directives $compile(element)(scope); }; return { priority: 1001, // Run before other directives eg ng-repeat terminal: true, // Stop other directives running link: linker }; } ]); 
 <!doctype html> <html ng-app="plunker"> <head> <script src="//code.angularjs.org/1.2.20/angular.js"></script> <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script> <script src="example.js"></script> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> </head> <body> <div data-ng-controller="DatepickerDemoCtrl"> <select data-ng-options="s for s in selects" data-ng-model="el" data-dynamic-directives="[ { 'if' : 'selectIsRequired', 'ng-required' : '{{selectIsRequired}}' }, { 'tooltip-placement' : 'bottom' }, { 'if' : 'addTooltip()', 'tooltip' : '{{ dt() }}' } ]"> <option value=""></option> </select> </div> </body> </html> 

I wanted to add my solution since the accepted one didn't quite work for me. 我想添加我的解决方案,因为被接受的解决方案对我而言不太有效。

I needed to add a directive but also keep mine on the element. 我需要添加一条指令,但也要保留我的元素。

In this example I am adding a simple ng-style directive to the element. 在此示例中,我向元素添加了一个简单的ng-style指令。 To prevent infinite compile loops and allowing me to keep my directive I added a check to see if what I added was present before recompiling the element. 为了防止无限的编译循环并允许我保留指令,我在重新编译元素之前添加了检查以查看是否存在添加的内容。

angular.module('some.directive', [])
.directive('someDirective', ['$compile',function($compile){
    return {
        priority: 1001,
        controller: ['$scope', '$element', '$attrs', '$transclude' ,function($scope, $element, $attrs, $transclude) {

            // controller code here

        }],
        compile: function(element, attributes){
            var compile = false;

            //check to see if the target directive was already added
            if(!element.attr('ng-style')){
                //add the target directive
                element.attr('ng-style', "{'width':'200px'}");
                compile = true;
            }
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {  },
                post: function postLink(scope, iElement, iAttrs, controller) {
                    if(compile){
                        $compile(iElement)(scope);
                    }
                }
            };
        }
    };
}]);

Try storing the state in a attribute on the element itself, such as superDirectiveStatus="true" 尝试将状态存储在元素本身的属性中,例如superDirectiveStatus="true"

For example: 例如:

angular.module('app')
  .directive('superDirective', function ($compile, $injector) {
    return {
      restrict: 'A',
      replace: true,
      link: function compile(scope, element, attrs) {
        if (element.attr('datepicker')) { // check
          return;
        }
        var status = element.attr('superDirectiveStatus');
        if( status !== "true" ){
             element.attr('datepicker', 'someValue');
             element.attr('datepicker-language', 'en');
             // some more
             element.attr('superDirectiveStatus','true');
             $compile(element)(scope);

        }

      }
    };
  });

I hope this helps you. 我希望这可以帮助你。

There was a change from 1.3.x to 1.4.x. 从1.3.x更改为1.4.x。

In Angular 1.3.x this worked: 在Angular 1.3.x中可以这样工作:

var dir: ng.IDirective = {
    restrict: "A",
    require: ["select", "ngModel"],
    compile: compile,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {
        attributes["ngOptions"] = "a.ID as a.Bezeichnung for a in akademischetitel";
        scope.akademischetitel = AkademischerTitel.query();
    }
}

Now in Angular 1.4.x we have to do this: 现在在Angular 1.4.x中,我们必须这样做:

var dir: ng.IDirective = {
    restrict: "A",
    compile: compile,
    terminal: true,
    priority: 10,
};

function compile(tElement: ng.IAugmentedJQuery, tAttrs, transclude) {
    tElement.append("<option value=''>--- Kein ---</option>");
    tElement.removeAttr("tq-akademischer-titel-select");
    tElement.attr("ng-options", "a.ID as a.Bezeichnung for a in akademischetitel");

    return function postLink(scope: DirectiveScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes) {

        $compile(element)(scope);
        scope.akademischetitel = AkademischerTitel.query();
    }
}

(From the accepted answer: https://stackoverflow.com/a/19228302/605586 from Khanh TO). (摘自Khanh TO的已接受答案: https ://stackoverflow.com/a/19228302/605586)。

A simple solution that could work in some cases is to create and $compile a wrapper and then append your original element to it. 在某些情况下可能有效的简单解决方案是创建包装并对其进行$ compile,然后将原始元素附加到其中。

Something like... 就像是...

link: function(scope, elem, attr){
    var wrapper = angular.element('<div tooltip></div>');
    elem.before(wrapper);
    $compile(wrapper)(scope);
    wrapper.append(elem);
}

This solution has the advantage that it keeps things simple by not recompiling the original element. 该解决方案的优势在于,通过不重新编译原始元素,使事情变得简单。

This wouldn't work if any of the added directive's require any of the original element's directives or if the original element has absolute positioning. 如果任何添加的指令require任何原始元素的指令,或者原始元素具有绝对定位,则此方法将无效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM