How to execute parent directive before child directive?

angularjs, angularjs-directive, javascript

Solution

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 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.

Problem

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?

Original source