tarekahf
tarekahf

Reputation: 1002

How can you detect when HTML rendering is completed in AngularJS

I've done extensive research on this subject, but no matter what I do, I find it extremely difficult to achieve this objective.

I want to execute code when all elements have been fully rendered in AngularJS web application. I think I found solution suggesting to use routers and views, but I could not make that work on my case, as it seems it requires certain configuration.

When you have ng-repeat and a lot of nested directives that will generate HTML/Content based on various conditions using ng-if, I noticed that HTML rendering continues even after document ready event is fired or view content have been loaded ie $viewContentLoaded event is triggered.

The closest idea I have is to use $watch over the length of the children of the element of a given directive. Every time the $watch is executed, increment counter renderCount. Then, in another timer event, check if the counter renderCount didn't change over the past say 3-5 seconds, then we can make an assumption that rendering is done.

The code to watch for the children, and check if no more rendering is taking place, could be as follows:

app.directive('whenRenderingDone',  function($interval, $parse){
    return {
        link: function (scope, el, attrs) {
            var renderingCount = 0;
            function watchForChildren() {
                scope.$watch(function(){
                    return $(':input', el).length;
                }, function(newVal, oldVal){
                    if (newVal) {
                        renderingCount++;
                    }
                })
            }
            watchForChildren();
            //Check counter every 3 seconds, if no change since last time, this means rendering is done.
            var checkRenderingDone = $interval(function(){
                var lastCount = lastCount || -1;
                if (lastCount === renderingCount) {
                    var func = $parse(attrs.whenRenderingDone);
                    $interval.cancel(checkRenderingDone);
                    func(scope);
                }
                lastCount = renderingCount || -1;
            }, 3000);
        }
    }
});

I will try to implement the above approach, and if you have feedback please let me know.

Tarek

Upvotes: 4

Views: 5121

Answers (2)

tarekahf
tarekahf

Reputation: 1002

I developed the following directive which is working well under Chrome and IE11:

app.directive('whenRenderingDone',  function($timeout, $parse){
    return {
        link: function (scope, el, attrs) {
            var lastCount;
            var lastTimer = 5000; // Initial timeout
            //Check counter every few seconds, if no change since last time, this means rendering is done.
            var checkRenderingDone = function (){
                var mainPromiseResolved = scope.mainPromiseResolved;
                lastCount = lastCount || -1;
                if (lastCount === el.find('*').length && mainPromiseResolved) {
                    console.log('Rendering done, lastCount = %i', lastCount);
                    var func = $parse(attrs.whenRenderingDone);
                    func(scope);
                } else {
                    lastCount = el.find('*').length;
                    console.log('mainPromiseResolved = %s, lastCount %i', mainPromiseResolved, lastCount)
                    console.log('Rendering not yet done. Check again after %i seconds.', lastTimer/1000.00);
                    stopCheckRendering = $timeout(checkRenderingDone, lastTimer); 
                    lastTimer = lastTimer - 1000;
                    if (lastTimer <= 0) {
                        lastTimer = 1000;
                    }
                    return stopCheckRendering;
                }
            }
            var stopCheckRendering;
            stopCheckRendering = checkRenderingDone();
            el.on('$destroy', function() {
                if (stopCheckRendering) {
                    $timeout.cancel(stopCheckRendering);
                }
              });
        }
    }
});

I hope this will be of help to you, and if you have any comment to improve, please let me know. See this to give you an idea about how it is working.

Tarek

Upvotes: 2

nebulr
nebulr

Reputation: 671

You can use $$postDigest to run code after the digest cycle completes. You can read more about the scope lifecycle here

// Some $apply action here or simply entering the digest cycle
scope.$apply(function () { ... });
...
scope.$$postDigest(function () {
  // Run any code in here that will run after all the watches complete
  // in the digest cycle. Which means it runs once after all the 
  // watches manipulate the DOM and before the browser renders
});

Upvotes: 0

Related Questions