最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - DOM is not ready in a directive's link function. Is hacky Timeout the only solution? - Stack Overflow

programmeradmin3浏览0评论

I'm using an ng-repeat inside a directive's template.

myApp.directive("test", function () {
    return {
   restrict: 'C',
     scope: {
      bindVar: '='
    },
        template: '<div>\
<div class="item" ng-repeat="sel in bindVar">{{sel.display}}</div>\
     </div>',
     link: function ($scope, element, attrs) {


     //   setTimeout(function() { 
        alert($('.item').length); // <--- RETURNS 0, IF I ADD TIMEOUT RETURNS 3
   // },0);



     } // of link
    } // of return
});

/

However, when the link() function is called I don't seem to get access to the items that have been created. In order to do this I need to set a timeout of 0 (after that it works).

I read this in the following article: .html

I also saw a similar Stack Overflow answer where the OP marked Timeout as the answer: DOM elements not ready in AngularJS Directive's link() function

But c'mon, there's got to be another way!

I'm crossing my fingers that this hacky solution is wrong, and there's some way that angular provides a callback when the DOM has been created via a directive. Or do I really rely on .. timeouts? (really? :/)

I'm using an ng-repeat inside a directive's template.

myApp.directive("test", function () {
    return {
   restrict: 'C',
     scope: {
      bindVar: '='
    },
        template: '<div>\
<div class="item" ng-repeat="sel in bindVar">{{sel.display}}</div>\
     </div>',
     link: function ($scope, element, attrs) {


     //   setTimeout(function() { 
        alert($('.item').length); // <--- RETURNS 0, IF I ADD TIMEOUT RETURNS 3
   // },0);



     } // of link
    } // of return
});

http://jsfiddle/foreyez/t4590zbr/

However, when the link() function is called I don't seem to get access to the items that have been created. In order to do this I need to set a timeout of 0 (after that it works).

I read this in the following article: http://lorenzmerdian.blogspot./2013/03/how-to-handle-dom-updates-in-angularjs.html

I also saw a similar Stack Overflow answer where the OP marked Timeout as the answer: DOM elements not ready in AngularJS Directive's link() function

But c'mon, there's got to be another way!

I'm crossing my fingers that this hacky solution is wrong, and there's some way that angular provides a callback when the DOM has been created via a directive. Or do I really rely on .. timeouts? (really? :/)

Share Improve this question edited May 23, 2017 at 12:00 CommunityBot 11 silver badge asked Feb 10, 2015 at 1:49 Shai UIShai UI 52k77 gold badges218 silver badges316 bronze badges 12
  • Not sure what you're asking... seems to work fine for me plnkr.co/edit/FQB4VPsJx7bDD4v1CixU?p=preview – yangli-io Commented Feb 10, 2015 at 1:58
  • sorry see my updated question. i'm basically trying to access dom nodes with jquery in the link() function. this only works when i do a timeout. another way that i found that works is putting a $( document ).ready(function() { }); inside my link() function... hacky I know. and btw, i know i should be using angular but i prefer jquery for dom manips. – Shai UI Commented Feb 10, 2015 at 2:02
  • 1 Can you tell us what you intend to do once you get a hold of the dom element? Maybe we can e up a more angular way of doing what are are trying to do. – Sylvain Commented Feb 10, 2015 at 2:21
  • Can you not just use jqlite element object provided to the link function? I'm guessing here but maybe jquery is looking for something in the page, which doesn't happen until after your directive is linked. – hassassin Commented Feb 10, 2015 at 2:22
  • Well, yes - you have templateUrl, which loads asynchronously. You are creating a race condition with the timeout - so that is not a solution – New Dev Commented Feb 10, 2015 at 2:26
 |  Show 7 more ments

3 Answers 3

Reset to default 4

$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 piling 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 ment (pile.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: '<span ng-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.

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)
       }
   };
});


<table class="box-table" width="100%">
        <thead>
            <tr>
                <th class='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).

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

myApp.directive('myRepeatDirective', function() {
  return function(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>
<div class="item" ng-repeat="sel in bindVar" my-repeat-directive>{{sel.display}}</div>
</div>

http://jsfiddle/foreyez/t4590zbr/3/

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

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论