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. Higher numbers run first. The default priority is 0 if you don’t specify one.
EDIT: after the discussion, here's the complete working solution. The key was to remove the attribute: element.removeAttr("common-things");
, and also element.removeAttr("data-common-things");
(in case users specify data-common-things
in the html)
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
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):
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. 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.
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).
If we don't set terminal:true
and priority: 1000
, there is a chance that some directives are compiled before our custom directive. And when our custom directive uses $compile to compile the element => compile again the already compiled directives. This will cause unpredictable behavior especially if the directives compiled before our custom directive have already transformed the 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.
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