Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the post link function executed before the transcluded child link functions?

The timing of (pre/post)link functions in AngularJS are well defined in the documentation

Pre-linking function

Executed before the child elements are linked. Not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking.

Post-linking function

Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.

and this blog post clearly illustrates this expected order.

But this order does not seem to apply when using ng-transclude and nested directives.

Here is an example for a dropright element (See the Plunkr)

<!-- index.html -->
<dropright>
  <col1-item name="a">
    <col2-item>1</col2-item>
    <col2-item>2</col2-item>
  </col1-item>
  <col1-item name="b">
    ...
  </col1-item>
</dropright>

// dropright-template.html
<div id="col1-el" ng-transclude></div>
<div id="col2-el">
  <!-- Only angularJS will put elements in there -->
</div>

// col1-item-template.html
<p ng-transclude></p>

// col2-item-template.html
<div ng-transclude></div>

The dropright looks like

dropright

The directives write a log in the console when their link and controller functions are called. It usually displays:

expected

But sometimes (after few refreshes), the order is not as expected:

sometimes happen

The dropright post-link function is executed before the post-link function of its children.

It may be because, in my particular case, I am calling the dropright controller in the children's directives (See the Plunkr)

angular.module('someApp', [])

.directive('dropright', function() {
    return {
        restrict: 'E',
        transclude: 'true',
        controller: function($scope, $element, $attrs) {
            console.info('controller - dropright');

            $scope.col1Tab = [];
            $scope.col2Tab = [];

            this.addCol1Item = function(el) {
                console.log('(col1Tab pushed)');
                $scope.col1Tab.push(el);
            };

            this.addCol2Item = function(el) {
                console.log('(col2Tab pushed)');
                $scope.col2Tab.push(el);
            };
        },
        link: {
            post: function(scope, element, attrs) {
                console.info('post-link - dropright');
                // Here, I want to move some of the elements of #col1-el
                // into #col2-el
            }
        },
        templateUrl: 'dropright-tpl.html'
    };
})

.directive('col1Item', function($interpolate) {
    return {
        require: '^dropright',
        restrict: 'E',
        transclude: true,
        controller: function() {
            console.log('-- controller - col1Item');
        },
        link: {
            post: function(scope, element, attrs, droprightCtrl) {
                console.log('-- post-link - col1Item');
                droprightCtrl.addCol1Item(element.children()[0]);
            }
        },
        templateUrl: 'col1-tpl.html'
    };      
})

.directive('col2Item', function() {
    var directiveDefinitionObject = {
        require: '^dropright',
        restrict: 'E',
        transclude: true,
        controller: function() {
            console.log('---- controller - col2Item');
        },
        link: {
            post: function(scope, element, attrs, droprightCtrl) {
                console.log('---- post-link - col2Item');
                droprightCtrl.addCol2Item(element.children()[0]);
            }
        },
        templateUrl: 'col2-tpl.html'
    };
    return directiveDefinitionObject;
});

Is there any clean way to execute the link function of a directive after all the link functions of its children while using transclusion?

like image 674
Dr_Sam Avatar asked Aug 11 '14 10:08

Dr_Sam


People also ask

What are compile pre and post linking in Angularjs?

Pre-linking function Executed before the child elements are linked. Not safe to do DOM transformation since the compiler linking function will fail to locate the correct elements for linking. Post-linking function Executed after the child elements are linked.

What is $postLink in Angularjs?

$postLink() - Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.

How directives are compiled?

Each directive's compile function is only called once, when Angular bootstraps. Officially, this is the place to perform (source) template manipulations that do not involve scope or data binding. The <my-raw> directive will render a particular set of DOM markup.

What is link in directive?

Link: The link function deals with linking scope to the DOM. Using Code for Compile. While defining a custom directive we have the option to define a link against which either we can define a function or we have the option to assign an object which will have pre & post function.


1 Answers

This is my theory - its not the transclude aspect that is causing the sequence issue but rather the template being a templateUrl. The template needs to be resolved before the post link function get to act on it - hence we say post link function is safe to do DOM manipulation. While we are getting 304s for all the 3 templates - we do have to read them and it ultimately resolves the template promise.

I created a plunker with template instead of templateUrl to prove the corollary. I have hot refresh/plunker Stop/Run many times but I always get link - dropright at the end.

Plunker with template instead of templateUrl

I don't pretend to understand the compile.js code fully. However it does appear that in compileTemplateUrl function $http.success() resolves the template and then on success the applyDirectivesToNode function is called passing in postLinkFn.

https://github.com/angular/angular.js/blob/master/src/ng/compile.js

like image 123
bhantol Avatar answered Oct 11 '22 13:10

bhantol