1

I have a fairly simple directive from which I'm trying to query a separate (but related) DOM element.

Relevant markup:

<a href="#target-id" data-collapsible="isCollapsed">Open</a>
...
<div id="target-id">...</div>

Controller:

app.controller('MyController', ['$scope', function( $scope ) {
    $scope.isCollapsed = true;
}]);

Directive:

app.directive('collapsible', function() {
    return {
        restrict: 'A',
        link: function( scope, el, attrs ) {
            var target = attrs.href.slice(1); // "target-id"
            console.log(document.getElementById(target)); // this will be null
        }
    };
});

The issue is that the DOM query (document.getElementById(target)) is running before <div id="target-id">...</div> is available. I've so far only been able to work around the race-condition by wrapping my query in a $timeout with a 100-500ms delay, but this feels wrong.

Surely I'm doing something wrong and/or there must be a better way to go about this?

This SO thread outlines a similar issue, but a zero-delay timeout does not work for me.

Community
  • 1
  • 1
André Dion
  • 21,269
  • 7
  • 56
  • 60
  • @dcodesmith The issue may be that I'm dealing with two separate DOM elements but my directive is only bound to one, but I'd like to keep the order of the `` and `
    ` out of the equation so that the directive works in either scenario. Neither the `` or the `
    ` are injected—both are declared in my document.
    – André Dion Jul 30 '13 at 13:03
  • You've explained what happens in your code, but what are you trying to do exactly ? If you want to access the DOM in the link function, you will have to wait with a timeout, but I'm pretty sure you don't only want to log it to the console, why do you want to access this element ? – DotDotDot Jul 30 '13 at 13:14
  • @DotDotDot The `` that contains the directive controls the display state of the target `
    `.
    – André Dion Jul 30 '13 at 13:16
  • `$timeout` might be the only way to go – dcodesmith Jul 30 '13 at 13:18
  • Interesting, but in this fiddle your code works as you expect http://jsfiddle.net/RYT8h/1/ – Alexandrin Rus Jul 30 '13 at 13:20
  • @AlexandrinRus Yes, it works fine *sometimes*. In your example the app is extremely basic so the DOM element is available at the time that you're querying it out, but a race-condition still exists. – André Dion Jul 30 '13 at 13:26
  • 3
    @AndréDion If it's only for the display, have you considered using just ng-click/ng-hide/ng-show ? like `Open` and `
    ` . I made a fiddle http://jsfiddle.net/DotDotDot/vGCJF/ the first part is your code, when I tested, it logged the div correctly, but I can't be sure it will work everywhere, and the secon part is a version with a ng-show
    – DotDotDot Jul 30 '13 at 13:28
  • binding a click event looks like a good way to go if you want to use a directive - @DotDotDot – Alexandrin Rus Jul 30 '13 at 13:34
  • @DotDotDot That may be a better way to go about this. I'm essentially trying to create a directive that's similar to [this one](https://github.com/angular-ui/bootstrap/tree/master/src/collapse), but I'm not using Bootstrap nor do I need the transition. Please post your suggestion as an answer so I can upvote it. – André Dion Jul 30 '13 at 13:37
  • I suggest you use `angular.element("jquerylite selector here")` instead of `document.get...` – Eduard Gamonal Jul 30 '13 at 13:39
  • If you really want a directive, of course you can bind a 'click' event the element in the directive and then do what you want after the click :) – DotDotDot Jul 30 '13 at 13:39
  • 1
    @EduardGamonal [`angular.element`](http://docs.angularjs.org/api/angular.element) only accepts selectors if jQuery is included. I'm not showing it, but I do wrap the result from `document.getElementById` with `angular.element`, but it's not relevant to the issue I'm having. – André Dion Jul 30 '13 at 13:41

2 Answers2

2

So, as I told in the comments, a simple way to do it would be to use ng-click / ng-show , to toggle the display

<a href="#target-id" ng-click='visibleTargetId=!visibleTargetId'>Open</a>
<div id="target-id" ng-show='visibleTargetId'>

of course, it could be done with a function, but this require to set manually a variable, which is not really scalable

If you want to completely get rid of the variables and continue to use a directive you could also bind a click event in that directive. You can see the fiddle here : http://jsfiddle.net/DotDotDot/vGCJF/3/

I used vanilla JS, if you want to avoid loading jQuery, but it could also be used with jQuery's toggle() function for example

the code is quite simple, in your linking function in your directive :

 el.bind('click', function(e){
 //whatever you want to do

}

good luck

DotDotDot
  • 3,566
  • 2
  • 20
  • 22
0

Try to enclose your directive into $(function(){ ... }); this jQuery function is triggered after the DOM content is loaded

Vermicello
  • 308
  • 1
  • 6
  • 11
  • 1
    I'm not using jQuery and am fairly sure that wrapping the entire directive inside of a `DOMContentLoaded` callback is not a recommended practice with Angular. – André Dion Jul 30 '13 at 12:59
  • You could try to use the standard js onload function window.onload = function() {}; – Vermicello Aug 01 '13 at 07:35