Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to execute parent directive before child directive?

Tags:

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?

like image 705
parliament Avatar asked Feb 27 '14 21:02

parliament


People also ask

What is the order of execution of the Postlink function?

As per the angulars documentation, Post link function for "Parent" will be executed only once post link function for child directives has been executed.

How do I get parent scope in directive?

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.

What is restrict in AngularJS directive?

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!


1 Answers

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.

like image 184
Eliran Malka Avatar answered Sep 23 '22 01:09

Eliran Malka