Skip to content Skip to sidebar Skip to footer

Dom Is Not Ready In A Directive's Link Function. Is Hacky Timeout The Only Solution?

I'm using an ng-repeat inside a directive's template. myApp.directive('test', function () { return { restrict: 'C', scope: { bindVar: '=' }, template:

Solution 1:

$timeout is, in fact, a legitimate way to solve this when you use inline template (as opposed to templateUrl). It would not create a race condition.

What happens is, Angular goes over the DOM and collects directives and their pre- and post-link functions (by compiling the directives). Then, the link functions for each directive for each node (i.e. DOM element) are executed.

Normally, the template for the node (to which the directive applies) is already part of the DOM. And so, if you have the following directive:

.directive("foo", function(){
  return {
    template: '<span class="fooClass">foo</span>',
    link: function(scope, element){
      // prints "<span class="fooClass">foo</span>"
      console.log(element.html()); 
    }
  }
}

it can find the $(".fooClass") element.

However, if a directive uses transclude: 'element', like ng-if (ngIf.js) and ng-repeat (ngRepeat.js) directives do, Angular rewrites the directive as a comment (compile.js), and so $(".item") (in your example) is not there until ng-repeat places it there. They do so in their scope.$watch function (ngIf.js), depending on the value they are watching, and this may happen at the next digest cycle. So, even when your post-link function runs, the actual element that you are search for is still not there.

.directive("foo", function(){
  return {
    template: '<spanng-if="true"class="fooClass">foo</span>',
    link: function(scope, element){
      // prints "<!-- ngIf: true -->"
      console.log(element.html());
    }
  }
}

But it will be there - definitely - when $timeout runs.

Solution 2:

The way I do this is with another directive. As an example:

.directive('elementReady', function() {
   return {
       restrict: 'A',
       link: function(scope, elem, attr) {
           //In here, you can do things like:if(scope.$last) {
              //this element is the last element in an ng-repeat
           }
           if(scope.$first) {
              //first element in ng-repeat
           }

           //do jQuery and javascript calculations (elem has been added to the DOM at this point)
       }
   };
});


<tableclass="box-table"width="100%"><thead><tr><thclass='test'scope="col"ng-repeat="column in listcolumns"element-ready>{{column.title}}</th></tr></thead></table>

Obviously, you will need to customize how you propagate those events to your outer scope ($emit, through bound functions, etc).

Solution 3:

Taking Joe's Answer and another answer I found on stackoverflow I was able to do this by:

myApp.directive('myRepeatDirective', function() {
  returnfunction(scope, element, attrs) {
    if (scope.$last){
      scope.$emit('LastElem');
    }
  };
});

and then in my original link function:

$scope.$on('LastElem', function(event){
        alert($('.item').length);
    });

and template looks like:

<div><divclass="item"ng-repeat="sel in bindVar"my-repeat-directive>{{sel.display}}</div></div>

http://jsfiddle.net/foreyez/t4590zbr/3/

but I'm still not loving this solution.. seems kind of blehhh

Post a Comment for "Dom Is Not Ready In A Directive's Link Function. Is Hacky Timeout The Only Solution?"