Angularjs loading screen on ajax request

180,652

Solution 1

Instead of setting up a scope variable to indicate data loading status, it is better to have a directive does everything for you:

angular.module('directive.loading', [])

    .directive('loading',   ['$http' ,function ($http)
    {
        return {
            restrict: 'A',
            link: function (scope, elm, attrs)
            {
                scope.isLoading = function () {
                    return $http.pendingRequests.length > 0;
                };

                scope.$watch(scope.isLoading, function (v)
                {
                    if(v){
                        elm.show();
                    }else{
                        elm.hide();
                    }
                });
            }
        };

    }]);

With this directive, all you need to do is to give any loading animation element an 'loading' attribute:

<div class="loading-spiner-holder" data-loading ><div class="loading-spiner"><img src="..." /></div></div>

You can have multiple loading spinners on the page. where and how to layout those spinners is up to you and directive will simply turn it on/off for you automatically.

Solution 2

Here's an example. It uses the simple ng-show method with a bool.

HTML

<div ng-show="loading" class="loading"><img src="...">LOADING...</div>
<div ng-repeat="car in cars">
  <li>{{car.name}}</li>
</div>
<button ng-click="clickMe()" class="btn btn-primary">CLICK ME</button>

ANGULARJS

  $scope.clickMe = function() {
    $scope.loading = true;
    $http.get('test.json')
      .success(function(data) {
        $scope.cars = data[0].cars;
        $scope.loading = false;
    });
  }

Of course you can move the loading box html code into a directive, then use $watch on $scope.loading. In which case:

HTML:

<loading></loading>

ANGULARJS DIRECTIVE:

  .directive('loading', function () {
      return {
        restrict: 'E',
        replace:true,
        template: '<div class="loading"><img src="..."/>LOADING...</div>',
        link: function (scope, element, attr) {
              scope.$watch('loading', function (val) {
                  if (val)
                      $(element).show();
                  else
                      $(element).hide();
              });
        }
      }
  })

PLUNK: http://plnkr.co/edit/AI1z21?p=preview

Solution 3

I use ngProgress for this.

Add 'ngProgress' to your dependencies once you've included the script/css files in your HTML. Once you do that you can set up something like this, which will trigger when a route change was detected.

angular.module('app').run(function($rootScope, ngProgress) {
  $rootScope.$on('$routeChangeStart', function(ev,data) {
    ngProgress.start();
  });
  $rootScope.$on('$routeChangeSuccess', function(ev,data) {
    ngProgress.complete();
  });
});

For AJAX requests you can do something like this:

$scope.getLatest = function () {
    ngProgress.start();

    $http.get('/latest-goodies')
         .success(function(data,status) {
             $scope.latest = data;
             ngProgress.complete();
         })
         .error(function(data,status) {
             ngProgress.complete();
         });
};

Just remember to add 'ngProgress' to the controllers dependencies before doing so. And if you are doing multiple AJAX requests use an incremental variable in the main app scope to keep track when your AJAX requests have finished before calling 'ngProgress.complete();'.

Solution 4

using pendingRequests is not correct because as mentioned in Angular documentation, this property is primarily meant to be used for debugging purposes.

What I recommend is to use an interceptor to know if there is any active Async call.

module.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($q, $rootScope) {
        if ($rootScope.activeCalls == undefined) {
            $rootScope.activeCalls = 0;
        }

        return {
            request: function (config) {
                $rootScope.activeCalls += 1;
                return config;
            },
            requestError: function (rejection) {
                $rootScope.activeCalls -= 1;
                return rejection;
            },
            response: function (response) {
                $rootScope.activeCalls -= 1;
                return response;
            },
            responseError: function (rejection) {
                $rootScope.activeCalls -= 1;
                return rejection;
            }
        };
    });
}]);

and then check whether activeCalls is zero or not in the directive through a $watch.

module.directive('loadingSpinner', function ($http) {
    return {
        restrict: 'A',
        replace: true,
        template: '<div class="loader unixloader" data-initialize="loader" data-delay="500"></div>',
        link: function (scope, element, attrs) {

            scope.$watch('activeCalls', function (newVal, oldVal) {
                if (newVal == 0) {
                    $(element).hide();
                }
                else {
                    $(element).show();
                }
            });
        }
    };
});

Solution 5

The best way to do this is to use response interceptors along with custom directive. And the process can further be improved using pub/sub mechanism using $rootScope.$broadcast & $rootScope.$on methods.

As the whole process is documented in a well written blog article, I'm not going to repeat that here again. Please refer to that article to come up with your needed implementation.

Share:
180,652

Related videos on Youtube

Badhrinath Canessane
Author by

Badhrinath Canessane

Ardent, Avant-garde Androider ☕

Updated on June 05, 2020

Comments

  • Badhrinath Canessane
    Badhrinath Canessane about 4 years

    Using Angularjs , I need to show a loading screen (a simple spinner) until ajax request is complete. Please suggest any idea with a code snippet.

    • Ajay Beniwal
      Ajay Beniwal about 11 years
    • Frank Myat Thu
      Frank Myat Thu over 8 years
      Best practice is using $httpProvider.interceptors since it can handle when ajax request start and when it end . That is why $watch is no longer needed to to detect when ajax call start and end.
    • Siddharth
      Siddharth over 7 years
      how about you checkout my factory angular-httpshooter, it gives you better control for loaders and freezing UI
  • Sanjay D
    Sanjay D over 10 years
    here "loading" directive is being called twice, with $location.path(). First with current page and next with the destination page. What should be the reason?
  • Krzysztof Safjanowski
    Krzysztof Safjanowski about 9 years
    Any API response will kill animation. You should store additional information about request - response, and react only for that respond that was initialized by request.
  • Joao Polo
    Joao Polo about 9 years
    Excellent. And also, you can use jquery toggle on watch: elm.toggle(v). I have a $timeout session monitor, and for me, I need to show the spinner only after a half second. I will implements a css with transition to show more slowly the spinner.
  • morpheus05
    morpheus05 about 9 years
    If you get the "elm.show() is not a function" error, you must add jquery before loading angular.
  • KingOfHypocrites
    KingOfHypocrites almost 9 years
    The way you do scope.watch works for me wheras the accepted answer approach doesn't.
  • KingOfHypocrites
    KingOfHypocrites almost 9 years
    The way you do scope.watch doesn't work for me. I had to do it the way that jzm does it (by using '' around the name and omitting the scope prefix). Any ideas why?
  • Mo.
    Mo. over 8 years
    @jzm Such a great solution 😊, unfortunately this solution not working with ui-router, don't know why
  • Vadim Ferderer
    Vadim Ferderer about 8 years
    What if I need to async. load multiple different areas?
  • codelearner
    codelearner about 8 years
    @DavidLin great. works good. but the thing is, if i have requests different areas in same page, it will show all the loaders for all the areas instead of particular area. how to do for particular area of request
  • alamnaryab
    alamnaryab about 7 years
    I did this in one of my project worked fine, in another project i think due to heavy ajax request loading=true do not get set
  • User_3535
    User_3535 about 7 years
    how to turn off for a specific request