AngularJS:以编程方式将ngIf添加到指令的最佳做法是什么?

我想创build一个指令,根据来自服务的值(例如,检查用户angular色)来检查一个元素是否应该出现在dom中。

相应的指令如下所示:

angular.module('app', []).directive('addCondition', function($rootScope) { return { restrict: 'A', compile: function (element, attr) { var ngIf = attr.ngIf, value = $rootScope.$eval(attr.addCondition); /** * Make sure to combine with existing ngIf! * I want to modify the expression to be evalued by ngIf here based on a role * check for example */ if (ngIf) { value += ' && ' + ngIf; } attr.$set('ng-if', value); } }; }); 

最后,元素具有ng-if属性,但不知何故,它不适用于元素,它仍然存在于DOM中。 所以这显然是一个错误的方法。

这小提琴显示问题: http : //jsfiddle.net/L37tZ/2/

谁能解释为什么发生这种情况? 还有什么其他方式可以实现类似的行为吗? 应该考虑现有的ngIfs。

解:

用法: <div rln-require-roles="['ADMIN', 'USER']">I'm hidden when theses role requirements are not satifisfied!</div>

 .directive('rlnRequireRoles', function ($animate, Session) { return { transclude: 'element', priority: 600, terminal: true, restrict: 'A', link: function ($scope, $element, $attr, ctrl, $transclude) { var block, childScope, roles; $attr.$observe('rlnRequireRoles', function (value) { roles = $scope.$eval(value); if (Session.hasRoles(roles)) { if (!childScope) { childScope = $scope.$new(); $transclude(childScope, function (clone) { block = { startNode: clone[0], endNode: clone[clone.length++] = document.createComment(' end rlnRequireRoles: ' + $attr.rlnRequireRoles + ' ') }; $animate.enter(clone, $element.parent(), $element); }); } } else { if (childScope) { childScope.$destroy(); childScope = null; } if (block) { $animate.leave(getBlockElements(block)); block = null; } } }); } }; }); 

在指令中添加优先级非常重要,否则不会评估附加到该元素的其他指令!

你的问题的第一部分,“为什么?”,我可以回答:

你遇到的问题是,你不能dynamic地将指令应用于元素,而不需要在元素上调用$compile

如果在设置属性之后调用$compile(element)(element.scope()) ,则会遇到堆栈溢出,因为您正在编译自己,导致编译自己,从而导致自己编译等等。

第二部分,“如何实现”,我遇到了麻烦。 我尝试了一些方法(比如用嵌套的ng-if截取内容),但我无法得到你正在寻找的行为。

我想下一步可​​能是研究ng-if的代码,并尝试直接在你的指令中实现类似的东西。

这是获得它的第一个工作 。 不过,我希望它需要一些清理和修改,以使其运行得如何真正需要它。

你可以像这样在你自己的指令中重用ngIf

 /** @const */ var NAME = 'yourCustomIf'; yourApp.directive(NAME, function(ngIfDirective) { var ngIf = ngIfDirective[0]; return { transclude: ngIf.transclude, priority: ngIf.priority, terminal: ngIf.terminal, restrict: ngIf.restrict, link: function($scope, $element, $attr) { var value = $attr[NAME]; var yourCustomValue = $scope.$eval(value); $attr.ngIf = function() { return yourCustomValue; }; ngIf.link.apply(ngIf, arguments); } }; }); 

然后像这样使用它

 <div your-custom-if="true">This is shown</div> 

它将使用所有使用ngIf的“function”。

Joscha的回答非常好,但实际上,如果你使用ng,那么这是行不通的。 我使用了Joscha的代码,并添加了几行将其与现有的ng-if指令相结合:

 angular.module('myModule').directive('ifAuthenticated', ['ngIfDirective', 'User', function(ngIfDirective, User) { var ngIf = ngIfDirective[0]; return { transclude: ngIf.transclude, priority: ngIf.priority - 1, terminal: ngIf.terminal, restrict: ngIf.restrict, link: function(scope, element, attributes) { // find the initial ng-if attribute var initialNgIf = attributes.ngIf, ifEvaluator; // if it exists, evaluates ngIf && ifAuthenticated if (initialNgIf) { ifEvaluator = function () { return scope.$eval(initialNgIf) && User.isAuthenticated(); } } else { // if there's no ng-if, process normally ifEvaluator = function () { return User.isAuthenticated(); } } attributes.ngIf = ifEvaluator; ngIf.link.apply(ngIf, arguments); } }; }]); 

所以,如果可以,那么做一些事情:

 <input type="text" ng-model="test"> <div ng-if="test.length > 0" if-authenticated>Conditional div</div> 

而条件的div将只显示,如果你是authentication&testinginput不是空的。

有另一种方法来解决这个问题,使用模板function。 这需要jquery 1.6+才能正常工作。

代码的工作小提琴: http : //jsfiddle.net/w72P3/6/

 return { restrict: 'A', replace: true, template: function (element, attr) { var ngIf = attr.ngIf; var value = attr.addCondition; /** * Make sure to combine with existing ngIf! */ if (ngIf) { value += ' && ' + ngIf; } var inner = element.get(0); //we have to clear all the values because angular //is going to merge the attrs collection //back into the element after this function finishes angular.forEach(inner.attributes, function(attr, key){ attr.value = ''; }); attr.$set('ng-if', value); return inner.outerHTML; } } 

replace:true可以防止embedded的元素。 如果没有replace = true,则模板函数返回的string被放入现有的html中。 即<a href="#" addCondition="'true'">Hello</a>变成<a href="#" ng-if="'true'"><a href="#" ng-if="'true'">Hello</a></a>

有关详细信息,请参阅https://docs.angularjs.org/api/ng/service/ $ compile。

 return { restrict: 'A', terminal: true, priority: 50000, // high priority to compile this before directives of lower prio compile: function compile(element, attrs) { element.removeAttr("add-condition"); // avoid indefinite loop element.removeAttr("data-add-condition"); return { pre: function preLink(scope, iElement, iAttrs, controller) { }, post: function postLink(scope, iElement, iAttrs, controller) { iElement[0].setAttribute('ng-if', iAttrs.addCondition); $compile(iElement)(scope); } }; } 

高优先级和terminal: true的组合是如何工作的基础:terminal标志告诉Angular在相同的HTML元素上跳过所有低优先级的指令。

这很好,因为我们想通过用ng-ifreplaceadd-condition来修改元素, ng-if在调用compile之前,那么将会处理ng-if和其他指令。