AngularJS directive runs before element is fully loaded

11,956

Solution 1

You can't, in a general sense, be ever "fully sure" by just having a directive on the <table> element.

But you can be sure in certain cases. In your case, if the inner content is ng-repeat-ed, then if the array of items over which ngRepeat works is ready, then the actual DOM elements will be ready at the end of the digest cycle. You can capture it after $timeout with 0 delay:

link: function(scope, element){
  $timeout(function(){
    console.log(element.find("tr").length); // will be > 0
  })
}

But, in a general sense, you can't be certain to capture the contents. What if the ngRepeated array is not there yet? Or what if there is an ng-include instead?

<table directive-name ng-include="'templates/tr.html'">
</table>

Or, what if there was a custom directive that worked differently than ngRepeat does?

But if you have full control of the contents, one possible way to know is to include some helper directive as the innermost/last element, and have it contact its parent directiveName when it's linked:

<table directive-name>
    <tr ng-repeat="...">
        <td ng-repeat="...">
          <directive-name-helper ng-if="$last">
        </td>
    </tr>
</table>
.directive("directiveNameHelper", function(){
  return {
    require: "?^directiveName",
    link: function(scope, element, attrs, ctrl){
      if (!ctrl) return;

      ctrl.notifyDone();
    }
  }
})

Solution 2

Try wrapping in a $timeout the code from your link function as it will execute after the DOM is rendered.

$timeout(function () {
    //do your stuff here as the DOM has finished rendering already
});

Don't forget to inject $timeout in your directive:

.directive("directiveName", function($timeout) {

There are plenty of alternatives but I think this one is cleaner as the $timeout executes after the rendering engine has finished its job.

Share:
11,956
Robert Kusznier
Author by

Robert Kusznier

Programmer, web developer, vegan. Born in 1989 in Poland. My profiles: GitHub | LeetCode What I like: Creating things, which are useful and solve real problems that our world faces, Learning and trying new things, both in programming and other fields, Clean code, good architecture, good design, JavaScript, Python, Haskell, Vim, People and other creatures, Cooking, David Lynch, Béla Tarr, Tom Waits and other cinema and music, Honesty. What I don't like: Writing and working on crappy code, Things that don't work as they should, Laziness, Wasting things.

Updated on June 15, 2022

Comments

  • Robert Kusznier
    Robert Kusznier about 2 years

    I have a directive attached to a dynamically generated <table> element inside a template. The directive manipulates the DOM of that table inside a link function. The problem is that the directive runs before the table is rendered (by evaluating ng-repeat directives) - the table is empty then.

    Question

    How can I make sure that the directive is ran after the table has been fully rendered?

    <table directive-name>
        <tr ng-repeat="...">
            <td ng-repeat="..."></td>
        </tr>
    </table>
    
    
    module.directive("directiveName", function() {
        return {
            scope: "A",
            link: function(scope, element, attributes) {
                /* I need to be sure that the table is already fully
                   rendered when this code runs */
            }
        };
    });
    
  • Robert Kusznier
    Robert Kusznier almost 9 years
    That is the most comprehensive answer here, I think. Thanks. I still don't get why $timeout with 0 delay guarantees that DOM will be ready, but I guess I'll find that in Angular's $timeout docs.
  • New Dev
    New Dev almost 9 years
    @Robert, ng-repeat has a $scope.$watchCollection - this fires after the link phase, and if the array is ready, then it transcludes the ng-repeat-ed template and places it in the DOM. $timeout with 0 delay executes right after that
  • jusopi
    jusopi almost 9 years
    _.defer simply calls setTimout with no delay, albeit it does some run-time checks on the parameters you pass in. Unless you're using lodash already, you might as well use NG's built in $timeout with no delay.