Angularjs loading screen on ajax request

AngularjsAngularjs Directive

Angularjs Problem Overview


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.

Angularjs Solutions


Solution 1 - Angularjs

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 - Angularjs

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 - Angularjs

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 - Angularjs

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 - Angularjs

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.

Solution 6 - Angularjs

In reference of this answer

https://stackoverflow.com/a/17144634/4146239

For me is the best solution but there's a way to avoid use jQuery.

.directive('loading', function () {
      return {
        restrict: 'E',
        replace:true,
        template: '<div class="loading"><img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" width="20" height="20" />LOADING...</div>',
        link: function (scope, element, attr) {
              scope.$watch('loading', function (val) {
                  if (val)
                      scope.loadingStatus = 'true';
                  else
                      scope.loadingStatus = 'false';
              });
        }
      }
  })

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

<body ng-app="myApp" ng-controller="myController" ng-init="loadingStatus='true'">
        <loading ng-show="loadingStatus" ></loading>
  
        <div ng-repeat="car in cars">
          <li>{{car.name}}</li>
        </div>
        <button ng-click="clickMe()" class="btn btn-primary">CLICK ME</button>
  
</body>

You need to replace $(element).show(); and (element).show(); with $scope.loadingStatus = 'true'; and $scope.loadingStatus = 'false';

Than, you need to use this variable to set the ng-show attribute of the element.

Solution 7 - Angularjs

Typescript and Angular Implementation

directive

((): void=> {
    "use strict";
    angular.module("app").directive("busyindicator", busyIndicator);
    function busyIndicator($http:ng.IHttpService): ng.IDirective {
        var directive = <ng.IDirective>{
            restrict: "A",
            link(scope: Scope.IBusyIndicatorScope) {
                scope.anyRequestInProgress = () => ($http.pendingRequests.length > 0);
                scope.$watch(scope.anyRequestInProgress, x => {            
                    if (x) {
                        scope.canShow = true;
                    } else {
                        scope.canShow = false;
                    }
                });
            }
        };
        return directive;
    }
})();

Scope

   module App.Scope {
        export interface IBusyIndicatorScope extends angular.IScope {
            anyRequestInProgress: any;
            canShow: boolean;
        }
    }  

Template

<div id="activityspinner" ng-show="canShow" class="show" data-busyindicator>
</div>

CSS
#activityspinner
{
    display : none;
}
#activityspinner.show {
    display : block;
    position : fixed;
    z-index: 100;
    background-image : url('data:image/gif;base64,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') 
    -ms-opacity : 0.4;
    opacity : 0.4;
    background-repeat : no-repeat;
    background-position : center;
    left : 0;
    bottom : 0;
    right : 0;
    top : 0;
}

Solution 8 - Angularjs

If you are using Restangular (which is awesome) you can create an animation during api calls. Here is my solution. Add a response interceptor and a request interceptor that sends out a rootscope broadcast. Then create a directive to listen for that response and request.:

         angular.module('mean.system')
  .factory('myRestangular',['Restangular','$rootScope', function(Restangular,$rootScope) {
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('http://localhost:3000/api');
      RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
        var extractedData;
        // .. to look for getList operations
        if (operation === 'getList') {
          // .. and handle the data and meta data
          extractedData = data.data;
          extractedData.meta = data.meta;
        } else {
          extractedData = data.data;
        }
        $rootScope.$broadcast('apiResponse');
        return extractedData;
      });
      RestangularConfigurer.setRequestInterceptor(function (elem, operation) {
        if (operation === 'remove') {
          return null;
        }
        return (elem && angular.isObject(elem.data)) ? elem : {data: elem};
      });
      RestangularConfigurer.setRestangularFields({
        id: '_id'
      });
      RestangularConfigurer.addRequestInterceptor(function(element, operation, what, url) {
        $rootScope.$broadcast('apiRequest');
        return element;
      });
    });
  }]);

Here is the directive:

        angular.module('mean.system')
  .directive('smartLoadingIndicator', function($rootScope) {
    return {
      restrict: 'AE',
      template: '<div ng-show="isAPICalling"><p><i class="fa fa-gear fa-4x fa-spin"></i>&nbsp;Loading</p></div>',
      replace: true,
      link: function(scope, elem, attrs) {
        scope.isAPICalling = false;

        $rootScope.$on('apiRequest', function() {
          scope.isAPICalling = true;
        });
        $rootScope.$on('apiResponse', function() {
          scope.isAPICalling = false;
        });
      }
    };
  })
;

Solution 9 - Angularjs

Include this in your "app.config":

 $httpProvider.interceptors.push('myHttpInterceptor');

And add this code:

app.factory('myHttpInterceptor', function ($q, $window,$rootScope) {
    $rootScope.ActiveAjaxConectionsWithouthNotifications = 0;
    var checker = function(parameters,status){
            //YOU CAN USE parameters.url TO IGNORE SOME URL
            if(status == "request"){
                $rootScope.ActiveAjaxConectionsWithouthNotifications+=1;
                $('#loading_view').show();
            }
            if(status == "response"){
                $rootScope.ActiveAjaxConectionsWithouthNotifications-=1;
              
            }
            if($rootScope.ActiveAjaxConectionsWithouthNotifications<=0){
                $rootScope.ActiveAjaxConectionsWithouthNotifications=0;
                $('#loading_view').hide();
                
            }
          
        
    };
return {
    'request': function(config) {
        checker(config,"request");
        return config;
    },
   'requestError': function(rejection) {
       checker(rejection.config,"request");
      return $q.reject(rejection);
    },
    'response': function(response) {
         checker(response.config,"response");
      return response;
    },
   'responseError': function(rejection) {
        checker(rejection.config,"response");
      return $q.reject(rejection);
    }
  };
});

Solution 10 - Angularjs

Also, there is a nice demo that shows how can you use Angularjs animation in your project.

The link is here (See the top left corner).

It's an open source. Here is the link to download

And here is the link for tutorial;

My point is, go ahead and download the source files and then see how they have implemented the spinner. They might have used a little better aproach. So, checkout this project.

Solution 11 - Angularjs

Use angular-busy:

Add cgBusy as to your app / module:

angular.module('your_app', ['cgBusy']);

Add your promise to scope:

function MyCtrl($http, User) {
  //using $http
  this.isBusy = $http.get('...');
  //if you have a User class based on $resource
  this.isBusy = User.$save();
}

In your html template:

<div cg-busy="$ctrl.isBusy"></div>

Solution 12 - Angularjs

Here simple interceptor example, I set mouse on wait when ajax starts and set it to auto when ajax ends.

$httpProvider.interceptors.push(function($document) {
return {
 'request': function(config) {
     // here ajax start
     // here we can for example add some class or show somethin
     $document.find("body").css("cursor","wait");

     return config;
  },

  'response': function(response) {
     // here ajax ends
     //here we should remove classes added on request start

     $document.find("body").css("cursor","auto");

     return response;
  }
};
});

Code has to be added in application config app.config. I showed how to change mouse on loading state but in there it is possible to show/hide any loader content, or add, remove some css classes which are showing the loader.

Interceptor will run on every ajax call, so no need to create special boolean variables ( $scope.loading=true/false etc. ) on every http call.

Interceptor is using builded in angular jqLite https://docs.angularjs.org/api/ng/function/angular.element so no Jquery needed.

Solution 13 - Angularjs

Create a Directive with the show and size attributes ( you can add more also )

    app.directive('loader',function(){
	return {
  	restrict:'EA',
    scope:{
    	show : '@',
      size : '@'
    },
    template : '<div class="loader-container"><div class="loader" ng-if="show" ng-class="size"></div></div>'
  }
})

and in html use as

 <loader show="{{loader1}}" size="sm"></loader>

In the show variable pass true when any promise is running and make that false when request is completed. Active demo - [Angular Loader directive example demo in JsFiddle][1]

[1]: https://jsfiddle.net/btvypscw/4/ "Angular Loader directive example demo in JsFiddle"

Solution 14 - Angularjs

I built on @DavidLin's answer a little to simplify it - removing any dependency on jQuery in the directive. I can confirm this works as I use it in a production application

function AjaxLoadingOverlay($http) {

    return {
        restrict: 'A',
        link: function ($scope, $element, $attributes) {

            $scope.loadingOverlay = false;

            $scope.isLoading = function () {
                return $http.pendingRequests.length > 0;
            };

            $scope.$watch($scope.isLoading, function (isLoading) {
                $scope.loadingOverlay = isLoading;
            });
        }
    };
}   

I use a ng-show instead of a jQuery call to hide/show the <div>.

Here's the <div> which I placed just below the opening <body> tag:

<div ajax-loading-overlay class="loading-overlay" ng-show="loadingOverlay">
    <img src="Resources/Images/LoadingAnimation.gif" />
</div>

And here's the CSS that provides the overlay to block UI while a $http call is being made:

.loading-overlay {
    position: fixed;
    z-index: 999;
    height: 2em;
    width: 2em;
    overflow: show;
    margin: auto;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
}

.loading-overlay:before {
    content: '';
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.3);
}

/* :not(:required) hides these rules from IE9 and below */
.loading-overlay:not(:required) {
    font: 0/0 a;
    color: transparent;
    text-shadow: none;
    background-color: transparent;
    border: 0;
}

CSS credit goes to @Steve Seeger's - his post: https://stackoverflow.com/a/35470281/335545

Solution 15 - Angularjs

You could add a condition and then change it via the rootscope. Before your ajax request, you simply call $rootScope.$emit('stopLoader');

angular.module('directive.loading', [])
        .directive('loading',   ['$http', '$rootScope',function ($http, $rootScope)
        {
            return {
                restrict: 'A',
                link: function (scope, elm, attrs)
                {
                    scope.isNoLoadingForced = false;
                    scope.isLoading = function () {
                        return $http.pendingRequests.length > 0 && scope.isNoLoadingForced;
                    };
    
                    $rootScope.$on('stopLoader', function(){
                        scope.isNoLoadingForced = true;
                    })
    
                    scope.$watch(scope.isLoading, function (v)
                    {
                        if(v){
                            elm.show();
                        }else{
                            elm.hide();
                        }
                    });
                }
            };
    
        }]);

This is definatly not the best solution but it would still works.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBadhrinath CanessaneView Question on Stackoverflow
Solution 1 - AngularjsDavid LinView Answer on Stackoverflow
Solution 2 - AngularjsmnsrView Answer on Stackoverflow
Solution 3 - AngularjsNej KutcharianView Answer on Stackoverflow
Solution 4 - Angularjsuser919532View Answer on Stackoverflow
Solution 5 - AngularjsM N Islam ShihanView Answer on Stackoverflow
Solution 6 - AngularjsArgoView Answer on Stackoverflow
Solution 7 - AngularjsJameel MoideenView Answer on Stackoverflow
Solution 8 - AngularjsEnkodeView Answer on Stackoverflow
Solution 9 - AngularjsBratisLatasView Answer on Stackoverflow
Solution 10 - AngularjsIdrees KhanView Answer on Stackoverflow
Solution 11 - AngularjskrlView Answer on Stackoverflow
Solution 12 - AngularjsMaciej SikoraView Answer on Stackoverflow
Solution 13 - AngularjsPartha RoyView Answer on Stackoverflow
Solution 14 - AngularjsBernView Answer on Stackoverflow
Solution 15 - AngularjsUser_3535View Answer on Stackoverflow