17

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?

3
  • My answer is correct because it is sourced from an SO thread written by the author of Angular tackling this exact issue with screen flicker on load due to data bindings. Besides the 2 solutions I offered you may I also suggest using 1 way data bindings in the spots where you do not need 2 way data binding (if your like most people you only need 1 way data binding through most of your app if not all of it). This can be easily done by including a double colon inside of your data bound expressions like {{ ::member.name }} Commented Jun 27, 2015 at 0:11
  • Working demo and more importantly, correct solution added to my answer. Give up that bounty! Commented Jun 27, 2015 at 0:37
  • 1
    I think OP can read all the answers and then decide which one award the bounty (if any) all by him/herself? Commented Jun 28, 2015 at 14:58

5 Answers 5

14
+50

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.

Sign up to request clarification or add additional context in comments.

3 Comments

You can use CSS animations for the loader instead of an animated GIF and the loader will animate even while rendering. Also no need to set a 100ms delay, just $timeout with no delay will do the trick. Check it: jsfiddle.net/v65kqygf
While this is a bit of a hack/workaround, it is the easiest approach in order to guarantee display of spinner before rendering starts. My gif locks up though so going to change to CSS spinner.
@Umut Benzer is it possible to show spinner like this in each tab. In demo you have provided if I switch between the tabs then it freezes the screen until loading completes and i can not switch back to other tab which already been loaded. I was looking for same functionality just it is required in each tab independently so that I can switch between tabs freely. Could you provide any pointers on this, is it possible or not.
7

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)

1 Comment

I truly cannot believe how many people are advocating slowing down the app with a $timeout just to make it LOOK like the app is loading. Doesn't something sound wrong with that?
1

I usually solve this in my HTML using ng-if.

In your case this would become something like:

<ul>
  <span ng-if="!tabs">Content loading...</span>
  <span ng-if="tabs && tabs.length < 1">No tabs available</span>
  <span ng-if="tabs && tabs.length > 0">
    <li ng-repeat="tab in tabs" 
        ng-class="{active:isActiveTab(tab.url)}" 
        ng-click="onClickTab(tab)">{{tab.title}}
   </li>
  </span>
</ul>

5 Comments

It didnt solve the problem. here is fiddle showing your solution jsfiddle.net/eRGT8/1046 The problem is the loading spinner does not display while tab is rendering.
You have to apply this logic to the tab's content :) Sorry that i misunderstood.
What do you mean by applying this logic on tab content? I didn't get you.
The timeout is blocking your entire application making your loading spinner not show untill the end by the way...
Oh. which timeout? So do you know any solution?
1

I think that it's not posibble to show loading spinner while rendring the page. The rendering engine is single threaded. Almost everything, except network operations, happens in a single thread. In Firefox and safari this is the main thread of the browser. In chrome it's the tab process main thread. Network operations can be performed by several parallel threads.

You can read aboout that here

There is only one way to solve this problem - to redesign the page: to show only part of data, lazy loading or etc.

2 Comments

He is not asking for loader when the page loads but for a spinner while rendering content of a tab inside the page (this question is specific to angular) I'm pretty sure there has to be a way and i stumbled across a similar problem
Of course you can load templates asynchronously, but while browser renders content (works angular) spinner will not work in any case
0

You can make custom directive for each li example loading-counter , then add dependency service which will hold counter, and in each directive inside link function you can call

var app = angular.module('app', []);

app.controller('List', function(LoadingCounter) {

  this.tabs = [{
    title: "Test tab",
    url: "http://google.sk"
  }, {
    title: "Test tab",
    url: "http://google.sk"
  }, {
    title: "Test tab",
    url: "http://google.sk"
  }]

  this.LoadingCounter = LoadingCounter;

});

app.directive('spinningLoader', function(LoadingCounter) {
  return {
    restrict: 'A',
    scope: {
      max: "="
    },
    link: function(scope) {
      LoadingCounter.updateCounter(scope.max);
    }
  }
});

app.factory('LoadingCounter', function($timeout) {
  var service = {};

  service.currentCounter = 0;
  service.finished = false;
  service.updateCounter = function(max) {
    service.currentCounter++;
    if (service.currentCounter == max) {
      //example timeout
      $timeout(function() {
        service.finished = true;
      }, 2000);

    }
  }
  service.isFinished = function() {
    return service.finished;

  }
  return service;
});
.hidden {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<body ng-app="app" ng-controller="List as _">
  <div ng-if="!_.LoadingCounter.isFinished()">Loading</div>
  <ul ng-class="{'hidden' : !_.LoadingCounter.isFinished() }">
    <li spinning-loader max="_.tabs.length" ng-repeat="tab in _.tabs" ng-class="{active:isActiveTab(tab.url)}" ng-click="onClickTab(tab)">{{tab.title}}
    </li>
  </ul>
</body>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.