AngularJS: What's the best practice to add ngIf to a directive programmatically?

angularjs, angularjs-directive, javascript

Solution

The first part of your question, "why?", is something I can answer:

The problem you are running into is that you can't dynamically apply directives to elements without calling `$compile` on the element.

If you call `$compile(element)(element.scope())` after you set the attribute, you run into a stack overflow because you are compiling yourself, which cause you to compile yourself which causes you to compile yourself, etc.

The second part, "how else to achieve", I am having trouble with. I tried a couple of approaches (like transcluding the content with a nested `ng-if`) but I can't get exactly the behavior you are looking for.

I think the next step might be to study the code for ng-if and try to implement something similar directly in your directive.

Here is a first pass of getting it working. I expect it needs some cleanup and modification to get it working how you really want it, however.

Problem

I want to create a directive that checks if an element should be present in the dom based on a value coming from a service (e.g. check for a user role). The corresponding directive looks like this: ``` 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); } }; }); ``` At the end the element has the ng-if attribute attached but somehow it doesn't apply to the element and it is still existing in the dom. So this is obviously a wrong approach. This fiddle shows the problem: http://jsfiddle.net/L37tZ/2/ Who can explain why this happens? Is there any other way a similar behaviour could be achieved? Existing ngIfs should be considered. SOLUTION: Usage: `<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; } } }); } }; }); ``` It is very important to add the priority in the directive, otherwise other directives attached to that element are not evaluated!

Original source