Angularjs loading screen on ajax request
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.
Related videos on Youtube
Comments
-
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 about 11 yearshave a look at stackoverflow.com/questions/15033195/…
-
Frank Myat Thu over 8 yearsBest 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 over 7 yearshow about you checkout my factory angular-httpshooter, it gives you better control for loaders and freezing UI
-
-
Sanjay D over 10 yearshere "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 about 9 yearsAny 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 about 9 yearsExcellent. 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 about 9 yearsIf you get the "elm.show() is not a function" error, you must add jquery before loading angular.
-
KingOfHypocrites almost 9 yearsThe way you do scope.watch works for me wheras the accepted answer approach doesn't.
-
KingOfHypocrites almost 9 yearsThe 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. over 8 years@jzm Such a great solution 😊, unfortunately this solution not working with
ui-router
, don't know why -
Vadim Ferderer about 8 yearsWhat if I need to async. load multiple different areas?
-
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 about 7 yearsI 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 about 7 yearshow to turn off for a specific request