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

杀马特。学长 韩版系。学妹 提交于 2019-12-10 16:01:57

问题


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.net/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.com/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? :/)


回答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: '<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.




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


<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).




回答3:


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.net/foreyez/t4590zbr/3/

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



来源:https://stackoverflow.com/questions/28422752/dom-is-not-ready-in-a-directives-link-function-is-hacky-timeout-the-only-solut

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!