I am using the $http
service of AngularJS to make an Ajax request.
How can a spinner GIF (or another type of busy indicator) be shown while the Ajax request is executing?
I don't see anything like an ajaxstartevent
in the AngularJS documentation.
This question is related to
angularjs
ajax
busyindicator
.factory('authHttpResponseInterceptor', ['$q', function ($q) {
return {
request: function(config) {
angular.element('#spinner').show();
return config;
},
response : function(response) {
angular.element('#spinner').fadeOut(3000);
return response || $q.when(response);
},
responseError: function(reason) {
angular.element('#spinner').fadeOut(3000);
return $q.reject(reason);
}
};
}]);
.config(['$routeProvider', '$locationProvider', '$translateProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $translateProvider, $httpProvider) {
$httpProvider.interceptors.push('authHttpResponseInterceptor');
}
]);
in your Template
<div id="spinner"></div>
css
#spinner,
#spinner:after {
border-radius: 50%;
width: 10em;
height: 10em;
background-color: #A9A9A9;
z-index: 10000;
position: absolute;
left: 50%;
bottom: 100px;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
Used following intercepter to show loading bar on http request
'use strict';
appServices.factory('authInterceptorService', ['$q', '$location', 'localStorage','$injector','$timeout', function ($q, $location, localStorage, $injector,$timeout) {
var authInterceptorServiceFactory = {};
var requestInitiated;
//start loading bar
var _startLoading = function () {
console.log("error start loading");
$injector.get("$ionicLoading").show();
}
//stop loading bar
var _stopLoading = function () {
$injector.get("$ionicLoading").hide();
}
//request initiated
var _request = function (config) {
requestInitiated = true;
_startLoading();
config.headers = config.headers || {};
var authDataInitial = localStorage.get('authorizationData');
if (authDataInitial && authDataInitial.length > 2) {
var authData = JSON.parse(authDataInitial);
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
}
return config;
}
//request responce error
var _responseError = function (rejection) {
_stopLoading();
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
//request error
var _requestError = function (err) {
_stopLoading();
console.log('Request Error logging via interceptor');
return err;
}
//request responce
var _response = function(response) {
requestInitiated = false;
// Show delay of 300ms so the popup will not appear for multiple http request
$timeout(function() {
if(requestInitiated) return;
_stopLoading();
console.log('Response received with interceptor');
},300);
return response;
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.requestError = _requestError;
authInterceptorServiceFactory.response = _response;
return authInterceptorServiceFactory;
}]);
Here is my solution which i feel is alot easer that the other posted here. Not sure how "pretty" it is though, but it solved all my issues
I have a css style called "loading"
.loading { display: none; }
The html for the loading div can be whatever but I used some FontAwesome icons and the spin method there:
<div style="text-align:center" ng-class="{ 'loading': !loading }">
<br />
<h1><i class="fa fa-refresh fa-spin"></i> Loading data</h1>
</div>
On the elements that you want to hide you simply write this:
<something ng-class="{ 'loading': loading }" class="loading"></something>
and in the function i just set this on load.
(function (angular) {
function MainController($scope) {
$scope.loading = true
I am using SignalR so in the hubProxy.client.allLocks function (when its done going through the locks) I juts put
$scope.loading = false
$scope.$apply();
This also hides the {{someField}} when the page is loading since I am setting the loading class on load and AngularJS removes it afterwards.
Since the functionality of position:fixed changed recently, I had difficulty showing the gif loader above all elements, so I had to use angular's inbuilt jQuery.
Html
<div ng-controller="FetchController">
<div id="spinner"></div>
</div>
Css
#spinner {display: none}
body.spinnerOn #spinner { /* body tag not necessary actually */
display: block;
height: 100%;
width: 100%;
background: rgba(207, 13, 48, 0.72) url(img/loader.gif) center center no-repeat;
position: fixed;
top: 0;
left: 0;
z-index: 9999;
}
body.spinnerOn main.content { position: static;} /* and whatever content needs to be moved below your fixed loader div */
Controller
app.controller('FetchController', ['$scope', '$http', '$templateCache', '$location', '$q',
function($scope, $http, $templateCache, $location, $q) {
angular.element('body').addClass('spinnerOn'); // add Class to body to show spinner
$http.post( // or .get(
// your data here
})
.then(function (response) {
console.info('success');
angular.element('body').removeClass('spinnerOn'); // hide spinner
return response.data;
}, function (response) {
console.info('error');
angular.element('body').removeClass('spinnerOn'); // hide spinner
});
})
Hope this helps :)
Based on Josh David Miller response:
<body>
<header>
</header>
<div class="spinner" ng-show="loading">
<div class="loader" ></div>
</div>
<div ng-view=""></div>
<footer>
</footer>
</body>
Add this css:
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
border-bottom : 16px solid black;
width: 80px;
height: 80px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
position: absolute;
top: 45%;
left: 45%;
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner{
width: 100%;
height: 100%;
z-index: 10000;
position: absolute;
top: 0;
left: 0;
margin: 0 auto;
text-align: center;
vertical-align: middle;
background: white;
opacity: 0.6;
}
And just in your angular add:
$rootScope.loading = false; $rootScope.loading = true; -> when $http.get ends.
This is a simple way to show a spinner that does not require a third-party library, intercepters, or jQuery.
In the controller, set and reset a flag.
function starting() {
//ADD SPINNER
vm.starting = true;
$http.get(url)
.then(function onSuccess(response) {
vm.data = response.data;
}).catch(function onReject(errorResponse) {
console.log(errorResponse.status);
}).finally(function() {
//REMOVE SPINNER
vm.starting = false;
});
};
In the HTML, use the flag:
<div ng-show="vm.starting">
<img ng-src="spinnerURL" />
</div>
<div ng-hide="vm.starting">
<p>{{vm.data}}</p>
</div>
The vm.starting
flag is set true
when the XHR starts and cleared when the XHR completes.
This works well for me:
HTML:
<div id="loader" class="ng-hide" ng-show="req.$$state.pending">
<img class="ajax-loader"
width="200"
height="200"
src="/images/spinner.gif" />
</div>
Angular:
$scope.req = $http.get("/admin/view/"+id).success(function(data) {
$scope.data = data;
});
While the promise returned from $http is pending, ng-show will evaluate it to be "truthy". This is automatically updated once the promise is resolved... which is exactly what we want.
Adding onto @Adam's answer,
Use ng-show as suggested, however, in your case you want the functionality to have multiple requests and await all of them before the loader is hidden.
<span ng-show="pendingRequests > 0">
<img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" style="height:20px;"/>
</span>
And then in your controller:
$scope.pendingRequests++;
let url = '/whatever_Your_URL_Is'
$http.get(url)
.then(function(response) {
$scope.pendingRequests--;
})
If you're wrapping your api calls within a service/factory, then you can track the loading counter there (per answer and excellent simultaneous suggestion by @JMaylin), and reference the loading counter via a directive. Or any combination thereof.
yourModule
.factory('yourApi', ['$http', function ($http) {
var api = {}
//#region ------------ spinner -------------
// ajax loading counter
api._loading = 0;
/**
* Toggle check
*/
api.isOn = function () { return api._loading > 0; }
/**
* Based on a configuration setting to ignore the loading spinner, update the loading counter
* (for multiple ajax calls at one time)
*/
api.spinner = function(delta, config) {
// if we haven't been told to ignore the spinner, change the loading counter
// so we can show/hide the spinner
if (NG.isUndefined(config.spin) || config.spin) api._loading += delta;
// don't let runaway triggers break stuff...
if (api._loading < 0) api._loading = 0;
console.log('spinner:', api._loading, delta);
}
/**
* Track an ajax load begin, if not specifically disallowed by request configuration
*/
api.loadBegin = function(config) {
api.spinner(1, config);
}
/**
* Track an ajax load end, if not specifically disallowed by request configuration
*/
api.loadEnd = function (config) {
api.spinner(-1, config);
}
//#endregion ------------ spinner -------------
var baseConfig = {
method: 'post'
// don't need to declare `spin` here
}
/**
* $http wrapper to standardize all api calls
* @param args stuff sent to request
* @param config $http configuration, such as url, methods, etc
*/
var callWrapper = function(args, config) {
var p = angular.extend(baseConfig, config); // override defaults
// fix for 'get' vs 'post' param attachment
if (!angular.isUndefined(args)) p[p.method == 'get' ? 'params' : 'data'] = args;
// trigger the spinner
api.loadBegin(p);
// make the call, and turn of the spinner on completion
// note: may want to use `then`/`catch` instead since `finally` has delayed completion if down-chain returns more promises
return $http(p)['finally'](function(response) {
api.loadEnd(response.config);
return response;
});
}
api.DoSomething = function(args) {
// yes spinner
return callWrapper(args, { cache: true });
}
api.DoSomethingInBackground = function(args) {
// no spinner
return callWrapper(args, { cache: true, spin: false });
}
// expose
return api;
});
(function (NG) {
var loaderTemplate = '<div class="ui active dimmer" data-ng-show="hasSpinner()"><div class="ui large loader"></div></div>';
/**
* Show/Hide spinner with ajax
*/
function spinnerDirective($compile, api) {
return {
restrict: 'EA',
link: function (scope, element) {
// listen for api trigger
scope.hasSpinner = api.isOn;
// attach spinner html
var spin = NG.element(loaderTemplate);
$compile(spin)(scope); // bind+parse
element.append(spin);
}
}
}
NG.module('yourModule')
.directive('yourApiSpinner', ['$compile', 'yourApi', spinnerDirective]);
})(angular);
<div ng-controller="myCtrl" your-api-spinner> ... </div>
https://github.com/wongatech/angular-http-loader is a good project for this.
Example here http://wongatech.github.io/angular-http-loader/
The code below shows a template example/loader.tpl.html when a request is happening.
<div ng-http-loader template="example/loader.tpl.html"></div>
The following way will take note of all requests, and hide only once all requests are done:
app.factory('httpRequestInterceptor', function(LoadingService, requestCount) {_x000D_
return {_x000D_
request: function(config) {_x000D_
if (!config.headers.disableLoading) {_x000D_
requestCount.increase();_x000D_
LoadingService.show();_x000D_
}_x000D_
return config;_x000D_
}_x000D_
};_x000D_
}).factory('httpResponseInterceptor', function(LoadingService, $timeout, error, $q, requestCount) {_x000D_
function waitAndHide() {_x000D_
$timeout(function() {_x000D_
if (requestCount.get() === 0){_x000D_
LoadingService.hide();_x000D_
}_x000D_
else{_x000D_
waitAndHide();_x000D_
}_x000D_
}, 300);_x000D_
}_x000D_
_x000D_
return {_x000D_
response: function(config) {_x000D_
requestCount.descrease();_x000D_
if (requestCount.get() === 0) {_x000D_
waitAndHide();_x000D_
}_x000D_
return config;_x000D_
},_x000D_
responseError: function(config) {_x000D_
requestCount.descrease();_x000D_
if (requestCount.get() === 0) {_x000D_
waitAndHide();_x000D_
}_x000D_
var deferred = $q.defer();_x000D_
error.show(config.data, function() {_x000D_
deferred.reject(config);_x000D_
});_x000D_
return deferred.promise;_x000D_
}_x000D_
};_x000D_
}).factory('requestCount', function() {_x000D_
var count = 0;_x000D_
return {_x000D_
increase: function() {_x000D_
count++;_x000D_
},_x000D_
descrease: function() {_x000D_
if (count === 0) return;_x000D_
count--;_x000D_
},_x000D_
get: function() {_x000D_
return count;_x000D_
}_x000D_
};_x000D_
})
_x000D_
You can try something like this as well:
Create directive :
myApp.directive('loader', function () {
return {
restrict: 'A',
scope: {cond: '=loader'},
template: '<span ng-if="isLoading()" class="soft"><span class="fa fa-refresh fa-spin"></span></span>',
link: function (scope) {
scope.isLoading = function() {
var ret = scope.cond === true || (
scope.cond &&
scope.cond.$$state &&
angular.isDefined(scope.cond.$$state.status) &&
scope.cond.$$state.status === 0
);
return ret;
}
}
};
});
Then you add something like this to mainCtrl
// Return TRUE if some request is LOADING, else return FALSE
$scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
And HTML can looks like this:
<div class="buttons loader">
<span class="icon" loader="isLoading()"></span>
</div>
Here's a version using a directive
and ng-hide
.
This will show the loader during all calls via angular's $http
service.
In the template:
<div class="loader" data-loading></div>
directive:
angular.module('app')
.directive('loading', ['$http', function ($http) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (value) {
if (value) {
element.removeClass('ng-hide');
} else {
element.addClass('ng-hide');
}
});
}
};
}]);
by using the ng-hide
class on the element, you can avoid jquery.
Customize: add an interceptor
If you create a loading-interceptor, you can show/hide the loader based on a condition.
directive:
var loadingDirective = function ($rootScope) {
return function ($scope, element, attrs) {
$scope.$on("loader_show", function () {
return element.removeClass('ng-hide');
});
return $scope.$on("loader_hide", function () {
return element.addClass('ng-hide');
});
};
};
interceptor:
spinner
when response.background === true;
request
and/or response
to set $rootScope.$broadcast("loader_show");
or $rootScope.$broadcast("loader_hide");
No need to use a directive, no need to get complicated.
Here is the code to put next to submit button or wherever you want the spinner to be:
<span ng-show="dataIsLoading">
<img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" style="height:20px;"/>
</span>
And then in your controller:
$scope.dataIsLoading = true
let url = '/whatever_Your_URL_Is'
$http.get(url)
.then(function(response) {
$scope.dataIsLoading = false
})
if you want to show loader for every http request call then you can use angular interceptor to manage http request calls ,
here is a sample code
<body data-ng-app="myApp">
<div class="loader">
<div id="loader"></div>
</div>
<script>
var app = angular.module("myApp", []);
app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
return {
request: function ($config) {
$('.loader').show();
return $config;
},
response: function ($config) {
$('.loader').hide();
return $config;
},
responseError: function (response) {
return response;
}
};
}]);
app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
function ($stateProvider, $urlRouterProvider, $httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
</script>
</body>
If you are using ngResource, the $resolved attribute of an object is useful for loaders:
For a resource as follows:
var User = $resource('/user/:id', {id:'@id'});
var user = User.get({id: 1})
You can link a loader to the $resolved attribute of the resource object:
<div ng-hide="user.$resolved">Loading ...</div>
create directive with this code:
$scope.$watch($http.pendingRequests, toggleLoader);
function toggleLoader(status){
if(status.length){
element.addClass('active');
} else {
element.removeClass('active');
}
}
All answers are or to complicated, or need to set some variables on every request which is very wrong practice if we know the DRY concept. 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.
Sharing my version of the great answer from @bulltorious, updated for newer angular builds (I used version 1.5.8 with this code), and also incorporated @JMaylin's idea of using a counter so as to be robust to multiple simultaneous requests, and the option to skip showing the animation for requests taking less than some minimum number of milliseconds:
var app = angular.module('myApp');
var BUSY_DELAY = 1000; // Will not show loading graphic until 1000ms have passed and we are still waiting for responses.
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('busyHttpInterceptor');
})
.factory('busyHttpInterceptor', ['$q', '$timeout', function ($q, $timeout) {
var counter = 0;
return {
request: function (config) {
counter += 1;
$timeout(
function () {
if (counter !== 0) {
angular.element('#busy-overlay').show();
}
},
BUSY_DELAY);
return config;
},
response: function (response) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return response;
},
requestError: function (rejection) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return rejection;
},
responseError: function (rejection) {
counter -= 1;
if (counter === 0) {
angular.element('#busy-overlay').hide();
}
return rejection;
}
}
}]);
Just discovered the angular-busy
directive that shows a little loader depending on some async call.
For example, if you have to make a GET
, reference the promise in your $scope
,
$scope.req = $http.get('http://google.fr');
and call it like so :
<div cg-busy="req"></div>
Here is the GitHub.
You can also install it using bower
(don't forget to update your project dependencies):
bower install angular-busy --save
For page loads and modals, the easiest way is to use the ng-show
directive and use one of the scope data variables. Something like:
ng-show="angular.isUndefined(scope.data.someObject)".
Here, while someObject
is undefined, the spinner will show. Once the service returns with data and someObject
is populated, the spinner will return to its hidden state.
This really depends on your specific use case, but a simple way would follow a pattern like this:
.controller('MainCtrl', function ( $scope, myService ) {
$scope.loading = true;
myService.get().then( function ( response ) {
$scope.items = response.data;
}, function ( response ) {
// TODO: handle the error somehow
}).finally(function() {
// called no matter success or failure
$scope.loading = false;
});
});
And then react to it in your template:
<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
This is the easiest way to add a spinner i guess:-
You can use ng-show with the div tag of any one of these beautiful spinners http://tobiasahlin.com/spinkit/ {{This is not my page}}
and then you can use this kind of logic
//ajax start_x000D_
$scope.finderloader=true;_x000D_
_x000D_
$http({_x000D_
method :"POST",_x000D_
url : "your URL",_x000D_
data: { //your data_x000D_
_x000D_
}_x000D_
}).then(function mySucces(response) {_x000D_
$scope.finderloader=false;_x000D_
$scope.search=false; _x000D_
$scope.myData =response.data.records;_x000D_
});_x000D_
_x000D_
//ajax end _x000D_
_x000D_
<div ng-show="finderloader" class=spinner></div>_x000D_
//add this in your HTML at right place
_x000D_
Another solution to show loading between different url changes is:
$rootScope.$on('$locationChangeStart', function() {
$scope.loading++;
});
$rootScope.$on('$locationChangeSuccess', function() {
$timeout(function() {
$scope.loading--;
}, 300);
});
And then in the markup just toggle the spinner with ng-show="loading"
.
If you want to display it on ajax requests just add $scope.loading++
when the request starts and when it ends add $scope.loading--
.
You can use angular interceptor to manage http request calls
<div class="loader">
<div id="loader"></div>
</div>
<script>
var app = angular.module("myApp", []);
app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
return {
request: function ($config) {
$('.loader').show();
return $config;
},
response: function ($config) {
$('.loader').hide();
return $config;
},
responseError: function (response) {
return response;
}
};
}]);
app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
function ($stateProvider, $urlRouterProvider, $httpProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
</script>
Here is my implementation, as simple as a ng-show and a request counter.
It use a new service for all request to $http:
myApp.service('RqstSrv', [ '$http', '$rootScope', function($http, $rootScope) {
var rqstService = {};
rqstService.call = function(conf) {
$rootScope.currentCalls = !isNaN($rootScope.currentCalls) ? $rootScope.currentCalls++ : 0;
$http(conf).then(function APICallSucceed(response) {
// Handle success
}, function APICallError(response) {
// Handle error
}).then(function() {
$rootScope.currentCalls--;
});
}
} ]);
And then you can use your loader base on the number of current calls:
<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
Source: Stackoverflow.com