21

I'm trying to toggle (hide/show) a loading gif on every new route, so my logic would be:

  • routeChangeStart = show loading gif
  • routeChangeSuccess = hide loading gif

This is my code:

//ANGULAR
app.run(function($rootScope) {

   $rootScope.layout = {};
   $rootScope.layout.loading = false; 

   $rootScope.$on('$routeChangeStart', function() {

      //show loading gif
      $rootScope.layout.loading = true;

   });

   $rootScope.$on('$routeChangeSuccess', function() {

      //hide loading gif
      $rootScope.layout.loading = false;

   });

   $rootScope.$on('$routeChangeError', function() {

       //hide loading gif
       alert('wtff');
       $rootScope.layout.loading = false;
   });
});

//HTML

<img src="img/loading.gif" ng-hide="!layout.loading"/>

it is strange cause this works for 3/4 routes changed then it stop working while changing routes :O

what it could be?

HERE IS A LIVE EXAMPLE thanks to @Rob Sedgwick : http://plnkr.co/edit/ZpkgRhEAoUGlnXjbLb8b

itsme
  • 48,972
  • 96
  • 224
  • 345
  • 1
    Hi, interested to see the answer here. Here I am starting the 'loader' on 'click' ( links that change the route ) and hiding it from inside the template/controller ( eg. when it has loaded ). Works, but this is the way neater / DRY. – Rob Sedgwick Feb 02 '14 at 16:26
  • @RobSedgwick can't wait for answer tho lol , i tought it was cool to have a var toggling the gif let's see if someone can helps ;) – itsme Feb 02 '14 at 16:30
  • @RobSedgwick try it by yourself if you can i don't know why it won't work , it works for 3/4 route changes then it stops working :( – itsme Feb 02 '14 at 16:31
  • While your approach works, I personally feel that using a request/response interceptor works great for this. – Mark Feb 17 '14 at 08:43

2 Answers2

23

Short answer: the $rootScope.layout.loading changed as expected. But if the templates are loaded the browser has no time to make the changes visible.

Long answer: if you put console.log outputs in your $on listeners you will see, that a $routeChangeStart event fires, the template is loaded and after that $routeChangeSuccess fires. Under the hood the following happens:

  • $routeChangeStart fires
  • $rootScope.layout.loading becomes true
  • the template gets loaded asynchronously
  • the DOM is updated
  • the browser renders the loading gif

a little bit later (the xhr request returns)

  • the template was loaded
  • $routeChangeSuccess fires
  • $rootScope.layout.loading becomes false
  • the DOM is updated
  • the browser did no longer show the gif

what happens if the tmeplates are cached: - $routeChangeStart fires - $rootScope.layout.loading becomes true - $routeChangeSuccess fires - $rootScope.layout.loading becomes false - the DOM is updated (but this is the same state as before)

As you can see the browser can not render the new state because a script is running that is not interrupted to give the browser time to render the DOM. If the template is not yet loaded, the ajax requests stops the script execution and give's the browser time to render the DOM.

How to fix this?

You can use a timeout that give's the browser time to update the DOM:

$rootScope.$on('$routeChangeSuccess', function () {
    console.log('$routeChangeSuccess');
    //hide loading gif
    $timeout(function(){
      $rootScope.layout.loading = false;
    }, 200);
});

Here is your plunkr with this solution: http://plnkr.co/edit/MkREo0Xpz5SUWg0lFCdu?p=preview

michael
  • 16,221
  • 7
  • 55
  • 60
3

( A little too much to put in a comment )

I set the code up and saw that the loading variable was not updated each time and was due to the template caching, I guess the 'RouteChange' is not triggered.

Disabling the template caching will let your code run each time ..

app.run(function($rootScope, $location, $anchorScroll, $routeParams,$templateCache) {

   $rootScope.$on('$viewContentLoaded', function() {
       $templateCache.removeAll();
    });

   ....
Rob Sedgwick
  • 5,216
  • 4
  • 20
  • 36
  • mmmm do you know if it's possible to remove element from cache only? :P cause this seems to work now but i would like to cache content :P – itsme Feb 02 '14 at 16:54
  • There might be way to do a 'hard' check for 'routeChanged' - but I don't know what that is. sorry. Disabling cache to show a loading indicator feels a tad wrong. Why not trigger your own route change as an ng-click on links - route your route through a pre route - so to speak ( just guessing ideas ) – Rob Sedgwick Feb 02 '14 at 16:57
  • i just would like to make this work just cause i can't understand why it won't work :D then sure if i'll not be able to get it like this i'll look for some other work around :P thank you man ;) +1 – itsme Feb 02 '14 at 16:59
  • Updated @sbaaaang, looks like it needs the '$route' injected too, try that. Thanks for introducing this new way to me. – Rob Sedgwick Feb 02 '14 at 17:18
  • tryed but sometimes it stops working anyway :( i was trying making a plunkr here http://plnkr.co/YSsFTyZ521tU9OJoW38c for a live example but i can't get it to work :/ – itsme Feb 02 '14 at 17:36
  • Hi, From your example I created a fork - http://plnkr.co/edit/ZpkgRhEAoUGlnXjbLb8b - it stops working , you're right - I will remove the '$route' reference from here ( i had the caching off left in - : S ! – Rob Sedgwick Feb 02 '14 at 18:09