Is there a better solution than using a hacky Timeout for waiting on the DOM to be ready in a directive's link function?

Inside one of my custom directives, I have an ng-repeat in the 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
});

Check out this JSFiddle for reference

Despite calling the link() function, I'm unable to access the newly created items directly. A workaround involves setting a timeout of 0 milliseconds (after that it works).

I came across a blog post discussing this issue in detail: Handling DOM Updates in AngularJS

Additionally, there's a related Stack Overflow thread where using Timeout was marked as the solution: DOM elements not ready in AngularJS Directive's link() function

Hopefully, there's a more elegant approach than relying on timeouts. Is there a built-in method in Angular to trigger a callback when the DOM is fully generated by a directive? Or are timeouts the only way to go? (really? :/)

Answer №1

$timeout is a valid solution when using inline template instead of templateUrl, and it does not lead to any race conditions.

Angular traverses the DOM, gathers directives along with their pre- and post-link functions by compiling them. Subsequently, the link functions for each directive on each node (i.e., DOM element) are executed.

In general, the template corresponding to the node (to which the directive applies) is already present in the DOM. Therefore, if you have the following directive:

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

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

However, if a directive utilizes transclude: 'element', like ng-if and ng-repeat directives do, Angular converts the directive into a comment. As a result, $(".item") (as in your case) is not available until placed there by ng-repeat. This insertion occurs within their scope.$watch function, depending on the value being watched, potentially happening during the subsequent digest cycle. Hence, even after executing the post-link function, the actual sought-after element might still be absent.

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

Nevertheless, it will undoubtedly become available when $timeout is triggered.

Answer №2

A different approach I use involves implementing another directive. Here is an illustration:

.directive('elementReady', function() {
   return {
       restrict: 'A',
       link: function(scope, elem, attr) {
           //You can perform actions like:
           if(scope.$last) {
              //this element is the last one in an ng-repeat
           }
           if(scope.$first) {
              //first element in ng-repeat
           }

           //perform 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>

Of course, you will need to determine how to pass those events to your outer scope ($emit, using bound functions, etc).

Answer №3

By combining Joe's Answer with another helpful solution I discovered on stackoverflow, I was able to achieve the desired outcome:

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

In my original link function, I included:

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

My template now looks like this:

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

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

Despite implementing this solution, I still have reservations about its effectiveness.. it just doesn't quite satisfy me.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating a process to produce a random number and send it directly to an automated email

I'm currently utilizing a form builder from jqueryform.com to construct a registration form. My goal is to have each registered user assigned with a unique account number, which will be a randomly generated 6-digit number. Additionally, I want the pro ...

Using openssl stream with node.js

I am facing an issue with my server running on TLS 1.0. Whenever I run the command below, it keeps giving me an endless stream of data on the terminal: ~$ openssl s_client -connect 'serveraddress':5000 The output is a real-time XML data stream ...

To address the issue of input values not persisting in a Selenium iframe element, I am implementing a custom JavaScript event to

Attempting to initiate a JavaScript 'change' event on an element within an iframe. The following is the code snippet I have been using: // accessing the iframe window var iframeDoc = document.getElementsByTagName("iframe")[0].contentWin ...

Google Maps API is successfully loading from an HTML file, however, it is not functioning properly when accessed

I am facing an issue while trying to connect to the Google Maps API through an AngularJS application on my localhost. Despite finding the javascript file in the HTML and displaying 'test1' in the console, the `initMap` function is not being calle ...

Divide the table header column "th" into two separate columns

Can someone assist me in achieving the output displayed in the image? I want to separate the header th into two lines like the blue line shown. I need two headers for a single td column. Please help, thank you. https://i.sstatic.net/SwILH.png <style& ...

Using Jquery to target all elements except for their child elements

I have the following code: <div id="mn"> <span></span> <span> <span></span></span> <span></span> <span> <span></span></span> <span></span> </div& ...

Tips for toggling the visibility of a revolution slider based on current time using JavaScript

Currently, I am integrating the revolution slider into my WordPress website. My goal is to have the slider change according to the standard time of day. For instance, I want slider1 to display in the morning, slider2 at noon, slider3 in the evening, and sl ...

Using Ajax with Laravel

Currently, I am attempting to utilize Ajax in Laravel in order to display search results in the "search_results_div" div without requiring the user to navigate away from the page. Unfortunately, I have encountered the following error message: "Column not ...

Error encountered while using Jest, React, Typescript, and React-Testing-Library

I have set up a project using React/NextJS with Typescript and now I am incorporating unit testing with Jest and React Testing Library. One of the unit tests for my component is as follows: import React from 'react'; import '@testing-libra ...

What is the best way to divide data prior to uploading it?

I am currently working on updating a function that sends data to a server, and I need to modify it so that it can upload the data in chunks. The original implementation of the function is as follows: private async updateDatasource(variableName: strin ...

Switch the appearance between two elements

In my HTML table, I have different levels of content - from main "contents" to nested "sub-contents" and even deeper "sub-sub-content". My goal is to hide all sub-content within the content cell that I click on. <table> <tr class=' ...

Is there a way to identify if the parent page has completed loading while within an iframe?

Is there a way to detect properties or events within the iframe that can help determine this? The src of the iframe and the parent page belong to different domains. ...

I am facing issues connecting my Express Node server to my MongoDB database using Mongoose

Starting my backend journey, I keep encountering the same error with my server.js --> // Step 1 - Create a folder named backend // Step 2 - Run npm init -y // Step 3 - Open in VS Code // Step 4 - Install express using npm i express // Step 5 - Create se ...

The error message "GL_INVALID_OPERATION: Active draw buffers with missing fragment shader outputs" is alerting about a custom shader issue in

I am working on building a custom shader that will not be rendered. I specifically want to instruct the fragment shader not to write anything, therefore I am not setting gl_FragColor in the code. The shader program performs well on Firefox and Edge, howev ...

Resetting the Buefy datepicker

I am using a beautiful date picker in my project to retrieve the value deliveryDate. The date is displayed with an option to clear it using a button that sets the date to null when clicked. However, I am encountering errors related to prop types in the con ...

I'm having trouble executing the straightforward code I discovered on GitHub

https://github.com/Valish/sherdog-api Upon downloading the sherdog-api, I embarked on installing node.js as a prerequisite for using this new tool, despite having no prior experience with such software. Following that, I opened up the command prompt and n ...

Best practices for managing non-PrivateRoutes in React

In my app.js, I have set up a PrivateRoute that requires user login and only grants access if the cookie is set. However, I want to prevent users from accessing /login after they have logged in successfully. To achieve this, I implemented a LoginRoute wi ...

Establish a field alias with boundaries in AngularJS

I am currently working with a JavaScript object and attempting to display and edit its properties using templates. Here is a simple example: <div ng-init="friends = [ {name:'John', age:25, gender:'boy'}, {nam ...

Capturing HTML String with Html2Canvas

What is the proper way to input a valid HTML String into html2canvas? For example: var html = "<html><head></head><body><p>HI</p></body></html> This method can be seen in action on While Html2canvas is a ...

Tips for extracting CSS data with Selenium webdriver's executescript function

Just starting to learn Javascript in a Node environment... I'm trying to use the code snippet below to extract CSS values for a specific web element, but I'm having trouble parsing it in JavaScript. driver.executeScript(script, ele).then(p ...