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?
As per the angulars documentation, Post link function for "Parent" will be executed only once post link function for child directives has been executed.
The directive scope uses prefixes to achieve that. Using prefixes helps establish a two-way or one-way binding between parent and directive scopes, and also make calls to parent scope methods. To access any data in the parent scope requires passing the data at two places – the directive scope and the directive tag.
Restrict. Angular allows us to set a property named restrict on the object we return on our directive definition. We can pass through a string with certain letters letting Angular know how our directive can be used. function MyDirective() { return { restrict: 'E', template: '<div>Hello world!
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.
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 } } }; }
$compile
service on the AngularJS docs.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 the postLink()
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 the template
property instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With