Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs tab Loading spinner while rendering

I have a page with some tabs and each tab has large amount of angularjs bindings.This is sample page where i am facing issue.Each tabs take about 10 seconds to render.

So i planned to give a loading spinner while tab renders. So i planned to show loading spinner during click on the tab and remove the spinner at the end($last) of the ng-repeat.

In the ng-click on tab i activated the spinning loader

<ul>
    <li ng-repeat="tab in tabs" 
        ng-class="{active:isActiveTab(tab.url)}" 
        ng-click="onClickTab(tab)">{{tab.title}}
   </li>
</ul>

In controller

$scope.onClickTab = function (tab) {
     showLoader();
     $scope.currentTab = tab.url;
 }

To check ng-repeat is complete i have used below directive

.directive('onFinishRender', function ($timeout) {    
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {

                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                });
            }
        }
    }
});

$scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) {
        removeLoader();
});

showLoader and removeLoader are simple function which append and remove the div having a simple loading spinner.

function showLoader() {
    $('body').append('<div class="loader"></div>');
}

function removeLoader() {
    $('.loader').fadeOut(function () {
        $(this).remove();
    });
}

Expected result: Spinning loader to be shown when clicked on tab and appear till ng-repeat finishes.(i.e the clicked tab renders completely)

Actual result: The loader is not shown when clicked on tab and it appear almost at the end of ng-repaet and appear for a fraction of seconds. Here you can observe the said behavior. I think the page is not able to show the spinner due to the angular bindings process which makes page freeze.

Can anyone help me to resolve this?

like image 373
Navaneet Avatar asked Jun 19 '15 11:06

Navaneet


2 Answers

You can change your code like this:

$timeout(function() {
  $scope.currentTab = tab.url 
}, 100);

Demo: http://jsfiddle.net/eRGT8/1053/

What I do is, I push currentTab change to next digest cycle. (It's some kind of a hack, not a solution I proud of.) Therefore, in first digest cycle (before $timeout invoked) only loading is shown. In the next digest cycle, the blocking ng-repeat stuff starts working but since we make loading visible previously, it appears.

:)

This solves your problem, but running long running and blocking javascript that hangs browser completely is not a good user experience. Also, since browser is hang completely, the loading gif will not animate, only will be visible.

like image 180
Umut Benzer Avatar answered Sep 19 '22 06:09

Umut Benzer


I would STRONGLY recommend reading this SO thread first, written by the author of AngularJS himself which addresses the root problem.

Delaying AngularJS route change until model loaded to prevent flicker

Misko asks then answers his own question regarding this topic by suggesting the use of $routeProvider, like so:

 $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}). // <-- this is the connection when resolved
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});

Here is the finished product/solution you are seeking, which is an enhanced version of the AngularJS phone demo:

http://mhevery.github.io/angular-phonecat/app/#/phones

This requires NgRoute(), but is the correct approach. Using a $timeout here seems kind of hacky to me - Im not saying you need ngRoute however I am not sure how to pull it off without it (and I DO realize that your tabs may not be routed currently but I hope it helps anyways).

Furthermore, may also find using angular.element(document).ready(function(){}); for your initial payload will also solve your woahs.

angular.element(document).ready(function(){
  $('.loading').remove(); // Just an example dont modify the dom outside of a directive!
  alert('Loaded!');
});

As you can see no $timeout(function(){}); is used either approach, here is proof of the angular.element.ready() works - without a router:

WORKING DEMO http://codepen.io/nicholasabrams/pen/MwOMNR

*if you reaaally need me to I can make some tabs up but you didn't give a working code sample in your post so I used another demo I made for another thread to show the solution.

HERE IS YET ANOTHER SOLUTION, this time I used your example!

http://jsfiddle.net/eRGT8/1069/

This time I fire a function when the $index === $last (last element in ngRepeat()) this way the loader is removed from the dom or hidden when the repeat is finished doing its business)

like image 30
Alpha G33k Avatar answered Sep 21 '22 06:09

Alpha G33k