Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to invoke a function in an Angular directive after the child nodes have been processed?

I'm trying to use an Angular JS directive to center the contents of a div.

This is the simplified version of the template:

<div id="mosaic" class="span12 clearfix" s-center-content>
    <div ng-repeat="item in hits" id="{{item._id}}" >
    </div>
</div>

This is the directive:

 module.directive('sCenterContent', function() {
    var refresh = function(element){
        var aWidth= element.innerWidth();
        var cWidth= element.children()[0].offsetWidth;
        var cHeight= element.children()[0].offsetHeight;
        var perRow= Math.floor(aWidth/cWidth);
        var margin=(aWidth-perRow*cWidth)/2;
        if(perRow==0){
            perRow=1;
        }
        var top=0;
        element.children().each(function(index, child){
            $(child).css('margin-left','0px');
            if((index % perRow)==0){
                $(child).css('margin-left',margin+'px');
            }
        });
    };
    return {
        link : function(scope, element, attrs) {
            $(window).resize(function(event){
                refresh(element);
            });
        }
    };
});

It basically floats some inner divs and adds a margin to the first div in each row, so the content is centered.

This works fine when the browser is resized. The problem comes when I try to do a refresh just after initialization.

I have tried with the post linking function as seen in this question. The pre link and post link functions get called in the expected order but not after the children of the elemenet with the s-center-content directive get rendered. The call to refresh fails as no children are found.

How can I make a call to refresh ensureing the children have been processed?

like image 745
Daniel Cerecedo Avatar asked Apr 10 '13 17:04

Daniel Cerecedo


2 Answers

I think using angular's $watch is a better, more elegant solution:

link : function(scope, element, attrs) {
            $(window).resize(function(event){
                refresh(element);
            });

            var watchCount = 0,
            unWatcher = $watch(
                function() {
                   return $('*[s-center-content] img').length !== 0;
                },
                function() {
                   // ensure refresh isn't called the first time (before the count of elements is > 0)
                   if (++watchCount === 1) {
                       return;
                   }

                   refresh(element);                   

                   // stop watching the element now that refresh has been called
                   unWatcher();
                }, true);
        }

About stop watching : AngularJS : Clear $watch

like image 114
threejeez Avatar answered Oct 23 '22 06:10

threejeez


This is a recurring issue (Somewhere there's an issue associated with this, as well as a thread in the google forums..)

Angular has no way to know when the dom is finished, it can only know when the functions that it calls return (and any promises are resolved). If those functions are asynchronous and don't have a callback or a promise, there's no way to be notified.

The way I've seen it done (and the way I've done it) is with a setInterval that checks the DOM periodically to see if it's in a completed state. I hate this solution and will be grateful for an alternative, but, it does get the job done.

like image 23
Roy Truelove Avatar answered Oct 23 '22 07:10

Roy Truelove