How to execute parent directive before child directive?
Reasoning
postLink()
is executed in reverse order, which means the child directive's postLink()
will be called before the parent's (i.e. depth first). For some reason, this is the default behavior (link()
actually refers to postLink()
). Luckily we also have preLink()
, which works the other way around - we can utilize that to our benefit.
To illustrate this - the following snippet of code:
app.directive('parent', function($log) {
return {
restrict: 'E',
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
$log.info('parent pre');
},
post: function postLink(scope, iElement, iAttrs, controller) {
$log.info('parent post');
}
}
}
};
});
app.directive('child', function($log) {
return {
restrict: 'E',
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
$log.info('child pre');
},
post: function postLink(scope, iElement, iAttrs, controller) {
$log.info('child post');
}
}
}
};
});
… will output the following:
> parent pre
> child pre
> child post
> parent post
See it live on plunker.
Solution
If we want the parent directive's logic to be performed before the child's, we will explicitly use preLink()
:
function SortableWidgetsDirective() {
return {
restrict: 'A',
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
iElement.find(".widget header").append($("<div class='widget-controls'></div>"));
iElement.sortable({});
},
post: angular.noop
}
}
};
}
function CloneableWidgetDirective() {
return {
restrict: 'A',
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
iElement.find("header .widget-controls").append($("<div class='clone-handle'></div>"));
},
post: angular.noop
}
}
};
}
References
-
$compile
service on the AngularJS docs.
Post Scriptum
You are correct, by the way -
priority
is meant for use with directives that share the same element.-
angular.noop
is just an empty method that returns nothing. If you still want to use thepostLink()
functions, just place the function declaration instead, as you would normally do, i.e.:post: function postLink(scope, iElement, iAttrs, controller) { ... }
Be ware of the use of
templateUrl
, as “ Because the template loading is asynchronous the compilation/linking is suspended until the template is loaded ” [source]. As a result, the order of execution will be disrupted. You can remedy this by including the template inlined in thetemplate
property instead.
Related videos on Youtube
![parliament](https://i.stack.imgur.com/myVMm.jpg?s=256&g=1)
parliament
Serial Entrepreneur Full Stack Developer (C#/AngularJS/D3.js) Contact me if you want to work on cool shit with me. Currently in the bitcoin space, starting a cryptocurrency exchange. Also have a body of work on automated trading systems. [email protected]
Updated on June 29, 2022Comments
-
parliament about 2 years
I'm looking to write two angular directives, a parent and a child directive, to create sortable and cloneable widgets. The intended markup is:
<div class="widget-container" data-sortable-widgets> <section class="widget" data-cloneable-widget></section> <div>
However, the child directive seems to execute before the parent, before a certain element is available (its added by the parent):
function SortableWidgetsDirective() { return { priority: 200, restrict: 'A', link: function ($scope, element, attrs) { element.find(".widget header").append($("<div class='widget-controls'></div>")); element.sortable({ }); } }; } function CloneableWidgetDirective() { return { priority: 100, restrict: 'A', link: function ($scope, element, attrs) { // This directive wrongfully executes first so widget-controls is no available element.find("header .widget-controls").append($("<div class='clone-handle'></div>")); } }; }
As you can see i tried setting priority but I think because they're on different elements, it does not work.
How can I make the parent execute first?
-
parliament over 10 yearsCan you please explain angular.noop? What if I still want to use link function after should it go instead of angular.noop or outside compile?
-
parliament over 10 yearsdo you know that this completely breaks down when supplying a templateUrl? plnkr.co/edit/1OlW6UuhvkfzD27yz2Vc?p=preview This is a problem for me as I need to use one. What do you suggest?
-
Eliran Malka over 10 yearsyes, see another update - it's worth noting that in the answer body.
-
parliament over 10 yearsactually my template is already on the page in a
<script type="text/ng-template" id="my-template></script>
. Can I force angular to not load it asynchronously to get around this? -
Eliran Malka over 10 yearsit's pre-loaded to the template cache, but the directive will load it regadless. so, no way out here - just put the template inlined. that's the limitations at the moment, i'm afraid (hope i'm proved wrong here...)
-
j_walker_dev about 10 yearsThe way i got around this was to use template, like mentioned. But to inject $templateCache and do template: $templateCache.get('url.tpl.html'). Neat little trick to keep the pre/post linking order.
-
Eliran Malka almost 10 yearssounds interesting, @j_walker_dev. please post it as an answer, so others may benefit from it :)
-
parliament over 8 yearsThis answer continues to be useful to me. I'd like to add the order of controller initialization for these directives. The order is: parent controller, parent pre, child controller, child pre, child post, parent post,