15

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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Dr_Sam
  • 1,818
  • 18
  • 24
  • What version of Angular are you using? There are a number of bug fixes to $compile specifically related to transclusion applied to release 1.2.18 and greater. See the release notes of 1.2.18 and above at https://github.com/angular/angular.js/blob/master/CHANGELOG.md#1218-ear-extendability-2014-06-13 – Beyers Aug 13 '14 at 19:04
  • @Beyers: We use the version 1.2.21 – Dr_Sam Aug 14 '14 at 05:44

2 Answers2

15

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

bhantol
  • 9,368
  • 7
  • 44
  • 81
  • So there are just no ways to use the templateUrl feature without this problem in the sequence? – Dr_Sam Aug 15 '14 at 11:39
  • 1
    I feel this is a defect or at least we need to fix the documentation saying that the postlinking functions are based on when the template is resolved where we say that they run in reverse order. This should be a side note in the AngularJS documentation because as it the concept is a bit complicated for some people. Created https://github.com/angular/angular.js/issues/8631 – bhantol Aug 15 '14 at 20:01
  • I am thinking that "direct" DOM manipulation of the inner/child DOMs in the postLink of the parent is probably not good idea because "when something happens `here` alter the DOM `there` seems a bit non-angularish. If you describe as to what you intend to do the the postLink of dropright (outer/parent) we can find a cleaner way. IMHO the cleaner way to add/subtract new DOMs is ngShow/ngIf if only we can device the meta model well. – bhantol Aug 15 '14 at 20:08
  • Thank your for opening the issue on GitHub. Indeed, we should not rely on the order of the post link functions for DOM manipulations. Thank you for the help! – Dr_Sam Aug 18 '14 at 06:14
0

This may be just weirdness with Plunker. I tried copying the files to my local IIS, and was not able to replicate the issue.

Michael Kang
  • 52,003
  • 16
  • 103
  • 135
  • We do have the bug locally, this is not just on Plunker. But it would be interesting to know what is different on your machine that makes it work. – Dr_Sam Aug 18 '14 at 06:08