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.
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)
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>
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.
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.
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>