Reputation: 6481
Is there a straight-forward, simple way to do the following -
<div class="my-class" my-custom-directive="{{evaluate expression}}"></div>
So that angular won't add the directive unless the expression is evaluated to true?
Edit:
The directive has to be an attribute so please, no solutions like
ng-if
with restrict: 'E'
,
ng-class
with restrict: 'C'
or ng-attr
- which doesn't work with custom directives.
Upvotes: 19
Views: 9507
Reputation: 13003
It's possible to do this by creating a directive with a high priority and terminal: true
. Then you can fiddle with the element attributes (add or remove them) and then recompile the element to let the directives run.
Here is the example as a plunk: http://plnkr.co/edit/DemVGr?p=info
Change the expression in the "directive-if" attribute to keep/remove the "logger" directive.
If the expression for an attribute evaluates to false
then it will be removed.
<div directive-if="{'logger': 'myValue == 1'}"
logger="testValue">
<p>"logger" directive exists? <strong>{{logger}}</strong></p>
</div>
Here is the directive implementation.
With some minor tweaking you could swap this around to add directives instead of removing them if that's what you would prefer.
/**
* The "directiveIf" directive allows other directives
* to be dynamically removed from this element.
*
* Any number of directives can be controlled with the object
* passed in the "directive-if" attribute on this element:
*
* {'attributeName': expression[, 'attribute': expression]}
*
* If `expression` evaluates to `false` then `attributeName`
* will be removed from this element.
*
* Usage:
*
* <any directive-if="{'myDirective': expression}"
* my-directive>
* </any>
*
*/
directive('directiveIf', ['$compile', function($compile) {
return {
// Set a high priority so we run before other directives.
priority: 100,
// Set terminal to true to stop other directives from running.
terminal: true,
compile: function() {
// Error handling - avoid accidental infinite compile calls
var compileGuard = 0;
return {
pre: function(scope, element, attr) {
// Error handling.
//
// Make sure we don't go into an infinite
// compile loop if something goes wrong.
compileGuard++;
if (compileGuard >= 10) {
console.log('directiveIf: infinite compile loop!');
return;
}
// End of error handling.
// Get the set of directives to apply.
var directives = scope.$eval(attr.directiveIf);
angular.forEach(directives, function(expr, directive) {
// Evaluate each directive expression and remove the directive
// attribute if the expression evaluates to `false`.
var result = scope.$eval(expr);
if (result === false) {
// Set the attribute to `null` to remove the attribute.
//
// See: https://docs.angularjs.org/api/ng/type/$compile.directive.Attributes#$set
attr.$set(directive, null)
}
});
/*
Recompile the element so the remaining directives can be invoked.
Pass our directive name as the fourth "ignoreDirective" argument
to avoid infinite compile loops.
*/
var result = $compile(element, undefined, undefined, 'directiveIf')(scope);
// Error handling.
//
// Reset the compileGuard after compilation
// (otherwise we can't use this directive multiple times).
//
// It should be safe to reset here because we will
// only reach this code *after* the `$compile()`
// call above has returned.
compileGuard = 0;
}
};
}
};
}]);
Upvotes: 5
Reputation: 831
@Sly_cardinal is right, used his code, but had to make a few adjustments:
(function () {
angular.module('MyModule').directive('directiveIf', function ($compile) {
// Error handling.
var compileGuard = 0;
// End of error handling.
return {
// Set a high priority so we run before other directives.
priority: 100,
// Set terminal to true to stop other directives from running.
terminal: true,
compile: function() {
return {
pre: function(scope, element, attr) {
// Error handling.
// Make sure we don't go into an infinite
// compile loop if something goes wrong.
compileGuard++;
if (compileGuard >= 10) {
console.log('directiveIf: infinite compile loop!');
return;
}
// Get the set of directives to apply.
var directives = scope.$eval(attr.directiveIf);
for (var key in directives) {
if (directives.hasOwnProperty(key)) {
// if the direcitve expression is truthy
if (directives[key]) {
attr.$set(key, true);
} else {
attr.$set(key, null);
}
}
}
// Remove our own directive before compiling
// to avoid infinite compile loops.
attr.$set('directiveIf', null);
// Recompile the element so the remaining directives
// can be invoked.
var result = $compile(element)(scope);
// Error handling.
//
// Reset the compileGuard after compilation
// (otherwise we can't use this directive multiple times).
//
// It should be safe to reset here because we will
// only reach this code *after* the `$compile()`
// call above has returned.
compileGuard = 0;
}
};
}
};
});
})();
Upvotes: 2
Reputation: 9469
Another approach is to create two versions of code - one when directive is needed and another one when it is not. And display using ng-if/ng-show one or another. Duplicate code can be moved to templates and these can be included.
Upvotes: 0