0

I've got a simple directive that draws a few elements, like in this example. I want to programatically set some style properties but in the link function, the elements are apparently not there yet.

Here's a fiddle.

What I think is happening is that when I call the colorSquares function, there are no squares yet in the DOM. Wrapping it in a $timeout, it works, but that just feels so wrong.

Is there any way I can be notified when the elements exist? Or is there a place that I can put the code which will access them that is guaranteed to run after they exist?

myApp.directive('myDirective', ['$timeout', function ($timeout) {
return {
    restrict: 'E',
    replace: false,
    link: function (scope, elem, attr) {

        scope.squares = [1,2,3,4,5];

        function colorSquares() {
            var squaresFromDOM = document.getElementsByClassName('square');
            for (var i = 0; i < squaresFromDOM.length; i++) {
                squaresFromDOM[i].style['background-color'] = '#44DD44';
            }
        }

        // this does not work, apparently because the squares are not in the DOM yet            
        colorSquares();

        // this works (usually).  It always works if I give it a delay that is long enough.           
        //$timeout(colorSquares);


    },
    template: '<div><div ng-repeat="s in squares" class="square"></div></div>'
};

}]);

Jer
  • 5,468
  • 8
  • 34
  • 41
  • I highly recommend reading [this post](http://stackoverflow.com/questions/14994391/thinking-in-angularjs-if-i-have-a-jquery-background). It'll make your life so much easier. – Pete Apr 02 '15 at 20:23

3 Answers3

0

You should work with Angular rather than against it which is to say you should use data bindings to do what you are trying to do rather than events/notifications in this context.

http://jsfiddle.net/efdwob3v/5/

link: function (scope, elem, attr) {
    scope.squares = [1,2,3,4,5];
    scope.style = {"background-color": "red"};
},
template: '<div><div ng-repeat="s in squares" class="square" ng-style="style"></div></div>'

That said there's no difference in doing the above and just using a different class that has that red background color or even just doing style="background-color: red;"

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
0

you put the answer in your qeustion, "It always works if I give it a delay that is long enough.".

So just make the delay long enough, in this situation that can be achieved by adding an onload event because when the elements get added to the DOM it calls that event.

So instead of just colorSquares(); you could use:

window.addEventListener("load", colorSquares);

Though this may not be the ideal solution since it will also trigger when something else triggers the onload event.

DutChen18
  • 1,133
  • 1
  • 7
  • 24
0

Answering your question directly. To know if an element is added to a directive or to the DOM in general, you can simply put a directive on that element, since the directive will run only when the element on which it "sits" is already in the DOM.

Using part of your code as an example:

myApp.directive('myDirective', function () {
    return {
        ...      
        //put custom directive that will notify when DOM is ready 
        template: '<div><div ng-repeat-ready ng-repeat="s in squares" class="square"></div></div>'
    };
});

And here is the custom ng-repeat-ready directive:

myApp.directive('ngRepeatReady', function () {
    return {
        link: function (scope) {
            if (scope.$last) {
                //do notification stuff here
                //for example $emit an event
                scope.$emit('ng-repeat is ready');
            }
        }
    }
});

This directive will run when the element on which is sits is already in the DOM and check if the element has $last property on the scope (ng-repeat sets this flag for the last element of the iterated object) which means that the ng-repeat directive is done and you can now operate on the DOM safely.

jcz
  • 903
  • 2
  • 9
  • 16