How to execute parent directive before child directive?

10,207

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.

Share:
10,207

Related videos on Youtube

parliament
Author by

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, 2022

Comments

  • parliament
    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
    parliament over 10 years
    Can 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
    parliament over 10 years
    do 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
    Eliran Malka over 10 years
    yes, see another update - it's worth noting that in the answer body.
  • parliament
    parliament over 10 years
    actually 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
    Eliran Malka over 10 years
    it'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
    j_walker_dev about 10 years
    The 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
    Eliran Malka almost 10 years
    sounds interesting, @j_walker_dev. please post it as an answer, so others may benefit from it :)
  • parliament
    parliament over 8 years
    This 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,