在AngularJS中添加来自指令的指令

我试图建立一个指令,负责添加更多的指令 ,它宣布的元素。 例如,我想构建一个指令,负责添加datepickerdatepicker-languageng-required="true"

如果我尝试添加这些属性,然后使用$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); } }; }); 

当然,如果我不$compile元素,属性将被设置,但指令不会被引导。

这种方法是正确的还是我做错了? 有没有更好的方法来实现相同的行为?

UDPATE :鉴于$compile是实现这一目的的唯一方法,是否有办法跳过第一次编译传递(该元素可能包含多个子项)? 也许通过设置terminal:true

更新2 :我已经尝试把指令放入一个select元素,并且如所期望的那样,编译运行两次,这意味着有两倍的预期option

如果您在单个DOM元素上有多个指令,并且它们的应用顺序很重要,则可以使用priority属性来对其应用程序进行排序。 更高的数字首先运行。 如果您不指定一个,则默认优先级为0。

编辑 :讨论后,这里是完整的工作解决方案。 关键是删除属性element.removeAttr("common-things"); ,还有element.removeAttr("data-common-things"); (如果用户在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); } }; } }; }); 

工作plunker可在: http ://plnkr.co/edit/Q13bUt?p=preview

要么:

 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

解释为什么我们必须设置terminal: truepriority: 1000 (高位):

当DOM准备就绪时, 如果这些指令位于同一个元素上,那么角逐行DOM来标识所有已注册的指令,并根据priority逐个编译指令。 我们将自定义指令的优先级设置为较高的数字,以确保它将首先被编译,并且使用terminal: true ,在编译该指令后,其他指令将被跳过

当我们的自定义指令被编译时,它将通过添加指令和删除自身来修改元素,并使用$ compile服务来编译所有指令(包括被跳过的指令)

如果我们没有设置terminal:truepriority: 1000 ,有一些机会,我们的自定义指令之前编译一些指令。 而当我们的自定义指令使用$ compile来编译元素=>再次编译已编译的指令。 这将导致不可预知的行为,特别是如果在我们的自定义指令之前编译的指令已经转换了DOM。

有关优先级和终端的更多信息,请查看如何理解指令的`terminal`?

ng-repeat (priority = 1000)也是修改模板的指令的一个例子,当ng-repeat被编译时, ng-repeat 会在其他指令被应用之前复制模板元素

感谢@ Izhaki的评论,这里是对ngRepeat源代码的参考: https : //github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js

你可以用一个简单的模板标签处理所有这些。 看一个例子http://jsfiddle.net/m4ve9/ 。 请注意,我实际上不需要super-directive定义上的编译或链接属性。

在编译过程中,Angular会在编译之前提取模板值,所以你可以在这里附加任何指令,Angular会为你处理。

如果这是需要保留原始内部内容的超级指令,则可以使用transclude : true并用<ng-transclude></ng-transclude>替换内部<ng-transclude></ng-transclude>

希望有所帮助,让我知道如果有什么不清楚的

亚历克斯

这是一个解决方案,它将需要动态添加的指令移动到视图中,并添加一些可选的(基本的)条件逻辑。 这使指令保持干净,没有硬编码的逻辑。

该指令需要一个对象数组,每个对象包含要添加的指令的名称以及要传递给它的值(如果有的话)。

我一直在努力思考这样一个指令的用例,直到我认为添加一些只添加基于某些条件的指令的条件逻辑(尽管下面的答案仍然有效)可能是有用的。 我添加了一个可选的if属性,该属性应该包含一个布尔值,表达式或函数(例如,在您的控制器中定义),以确定是否应该添加指令。

我还使用attrs.$attr.dynamicDirectives来获取用于添加指令的确切属性声明(例如data-dynamic-directivedynamic-directive ),而不用硬编码字符串值来检查。

Plunker演示

 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> 

我想添加我的解决方案,因为接受的解决方案对我来说并不合适。

我需要添加一个指令,但也保持我的元素。

在这个例子中,我正在给元素添加一个简单的ng样式指令。 为了防止无限的编译循环,并让我保持我的指令,我添加了一个检查,看看我添加之前是否存在重新编译元素。

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

尝试将状态存储在元素本身的属性中,例如superDirectiveStatus="true"

例如:

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

我希望这可以帮助你。

从1.3.x到1.4.x发生了变化。

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

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

(从可接受的答案:从Khanh TO https://stackoverflow.com/a/19228302/605586 )。

一个简单的解决方案可以在某些情况下工作是创建和$编译一个包装,然后追加你的原始元素。

就像是…

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

这个解决方案的优点是,通过不重新编译原始元素,它使事情变得简单。

如果任何添加的指令require任何原始元素的指令,或者原始元素具有绝对定位,则这将不起作用。