J'utilise le service $http
d'AngularJS pour effectuer une demande Ajax.
Comment un spinner GIF (ou un autre type d'indicateur occupé) peut-il être affiché pendant l'exécution de la demande Ajax?
Je ne vois rien comme un ajaxstartevent
dans la documentation d'AngularJS.
Voici les courant dernières incantations AngularJS:
angular.module('SharedServices', [])
.config(function ($httpProvider) {
$httpProvider.responseInterceptors.Push('myHttpInterceptor');
var spinnerFunction = function (data, headersGetter) {
// todo start the spinner here
//alert('start spinner');
$('#mydiv').show();
return data;
};
$httpProvider.defaults.transformRequest.Push(spinnerFunction);
})
// register the interceptor as a service, intercepts ALL angular ajax http calls
.factory('myHttpInterceptor', function ($q, $window) {
return function (promise) {
return promise.then(function (response) {
// do something on success
// todo hide the spinner
//alert('stop spinner');
$('#mydiv').hide();
return response;
}, function (response) {
// do something on error
// todo hide the spinner
//alert('stop spinner');
$('#mydiv').hide();
return $q.reject(response);
});
};
});
//regular angular initialization continued below....
angular.module('myApp', [ 'myApp.directives', 'SharedServices']).
//.......
Voici le reste (HTML/CSS) .... using
$('#mydiv').show();
$('#mydiv').hide();
pour le basculer. NOTE: ce qui précède est utilisé dans le module angular au début de l'article
#mydiv {
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:1000;
background-color:grey;
opacity: .8;
}
.ajax-loader {
position: absolute;
left: 50%;
top: 50%;
margin-left: -32px; /* -1 * image width / 2 */
margin-top: -32px; /* -1 * image height / 2 */
display: block;
}
<div id="mydiv">
<img src="lib/jQuery/images/ajax-loader.gif" class="ajax-loader"/>
</div>
Cela dépend vraiment de votre cas d'utilisation spécifique, mais un moyen simple suivrait un modèle comme celui-ci:
.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;
});
});
Et réagissez ensuite dans votre modèle:
<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
Voici une version utilisant un directive
et ng-hide
.
Cela montrera le chargeur pendant tous les appels via le service $http
de angular.
Dans le modèle:
<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');
}
});
}
};
}]);
en utilisant la classe ng-hide
sur l'élément, vous pouvez éviter jquery.
Personnaliser: ajoutez une interceptor
Si vous créez un intercepteur de chargement, vous pouvez afficher/masquer le chargeur en fonction d'une 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');
});
};
};
intercepteur:
spinner
quand response.background === true;
request
et/ou response
pour régler $rootScope.$broadcast("loader_show");
ou $rootScope.$broadcast("loader_hide");
Si vous utilisez ngResource, l'attribut $ resolu d'un objet est utile pour les chargeurs:
Pour une ressource comme suit:
var User = $resource('/user/:id', {id:'@id'});
var user = User.get({id: 1})
Vous pouvez lier un chargeur à l'attribut $ résolu de l'objet ressource:
<div ng-hide="user.$resolved">Loading ...</div>
https://github.com/wongatech/angular-http-loader est un bon projet pour cela.
Exemple ici http://wongatech.github.io/angular-http-loader/
Le code ci-dessous montre un exemple de modèle/loader.tpl.html lorsqu'une demande est en cours d'exécution.
<div ng-http-loader template="example/loader.tpl.html"></div>
Je viens de découvrir la directive angular-busy
qui montre un petit chargeur en fonction d’un appel asynchrone.
Par exemple, si vous devez créer un GET
, référencez la promesse dans votre $scope
,
$scope.req = $http.get('http://google.fr');
et appelez comme ça:
<div cg-busy="req"></div>
Voici la GitHub.
Vous pouvez également l'installer avec bower
(n'oubliez pas de mettre à jour les dépendances de votre projet):
bower install angular-busy --save
Si vous enregistrez vos appels d'API dans un service/une usine, vous pouvez suivre le compteur de chargement à cet endroit (par réponse et une excellente suggestion simultanée de @JMaylin), et référencer le compteur de chargement via une directive. Ou toute combinaison de ceux-ci.
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>
Pour les chargements de page et les modaux, le moyen le plus simple consiste à utiliser la directive ng-show
et à utiliser l'une des variables de données de portée. Quelque chose comme:
ng-show="angular.isUndefined(scope.data.someObject)".
Ici, bien que someObject
ne soit pas défini, le compteur s’affiche. Une fois que le service est revenu avec les données et que someObject
est rempli, le compteur retourne à son état masqué.
C'est le moyen le plus simple d'ajouter un disque, je suppose: -
Vous pouvez utiliser ng-show avec la balise div de n’importe laquelle de ces belles fileuses http://tobiasahlin.com/spinkit/ {{Ce n’est pas ma page}}
et alors vous pouvez utiliser ce genre de logique
//ajax start
$scope.finderloader=true;
$http({
method :"POST",
url : "your URL",
data: { //your data
}
}).then(function mySucces(response) {
$scope.finderloader=false;
$scope.search=false;
$scope.myData =response.data.records;
});
//ajax end
<div ng-show="finderloader" class=spinner></div>
//add this in your HTML at right place
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>
Ajouter ce 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;
}
Et juste dans votre angular ajoutez:
$ rootScope.loading = false; $ rootScope.loading = true; -> quand $ http.get se termine.
Partage de ma version de l'excellente réponse de @bulltorious, mise à jour pour les nouvelles constructions angular (j'ai utilisé la version 1.5.8 avec ce code), et a également intégré l'idée de @ JMaylin d'utiliser un compteur pour être robuste plusieurs requêtes simultanées et l'option permettant de ne pas afficher l'animation pour les demandes prenant moins d'un minimum de millisecondes:
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;
}
}
}]);
Vous pouvez utiliser angular interceptor pour gérer les appels de demandes http
<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>
Cela fonctionne bien pour moi:
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>
Angulaire:
$scope.req = $http.get("/admin/view/"+id).success(function(data) {
$scope.data = data;
});
Bien que la promesse renvoyée par $ http soit en attente, ng-show l’évaluera comme étant "la vérité". Ceci est automatiquement mis à jour une fois la promesse résolue ... ce qui est exactement ce que nous voulons.
C'est un moyen simple d'afficher un spinner qui ne nécessite pas de bibliothèque tierce, d'intercepteur ou de jQuery.
Dans le contrôleur, définissez et réinitialisez un indicateur.
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;
});
};
Dans le code HTML, utilisez le drapeau:
<div ng-show="vm.starting">
<img ng-src="spinnerURL" />
</div>
<div ng-hide="vm.starting">
<p>{{vm.data}}</p>
</div>
Le drapeau vm.starting
est défini sur true
au démarrage de la XHR et est effacé à la fin de celle-ci.
tilisé après intercepter pour afficher la barre de chargement sur la requête http
'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;
}]);
Une autre solution pour afficher le chargement entre différents changements d’URL est la suivante:
$rootScope.$on('$locationChangeStart', function() {
$scope.loading++;
});
$rootScope.$on('$locationChangeSuccess', function() {
$timeout(function() {
$scope.loading--;
}, 300);
});
Et ensuite, dans le balisage, il suffit de basculer la roulette avec ng-show="loading"
.
Si vous souhaitez l'afficher sur les requêtes ajax, ajoutez simplement $scope.loading++
au début et à la fin de la requête, ajoutez $scope.loading--
.
La manière suivante prend note de toutes les demandes et ne cache que lorsque toutes les demandes sont terminées:
app.factory('httpRequestInterceptor', function(LoadingService, requestCount) {
return {
request: function(config) {
if (!config.headers.disableLoading) {
requestCount.increase();
LoadingService.show();
}
return config;
}
};
}).factory('httpResponseInterceptor', function(LoadingService, $timeout, error, $q, requestCount) {
function waitAndHide() {
$timeout(function() {
if (requestCount.get() === 0){
LoadingService.hide();
}
else{
waitAndHide();
}
}, 300);
}
return {
response: function(config) {
requestCount.descrease();
if (requestCount.get() === 0) {
waitAndHide();
}
return config;
},
responseError: function(config) {
requestCount.descrease();
if (requestCount.get() === 0) {
waitAndHide();
}
var deferred = $q.defer();
error.show(config.data, function() {
deferred.reject(config);
});
return deferred.promise;
}
};
}).factory('requestCount', function() {
var count = 0;
return {
increase: function() {
count++;
},
descrease: function() {
if (count === 0) return;
count--;
},
get: function() {
return count;
}
};
})
Vous pouvez essayer quelque chose comme ça aussi:
Créer une 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;
}
}
};
});
Ensuite, vous ajoutez quelque chose comme ceci à mainCtrl
// Return TRUE if some request is LOADING, else return FALSE
$scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
Et HTML peut ressembler à ceci:
<div class="buttons loader">
<span class="icon" loader="isLoading()"></span>
</div>
créer une directive avec ce code:
$scope.$watch($http.pendingRequests, toggleLoader);
function toggleLoader(status){
if(status.length){
element.addClass('active');
} else {
element.removeClass('active');
}
}
si vous voulez afficher le chargeur pour chaque appel de requête http, vous pouvez utiliser l'intercepteur angular pour gérer les appels de requête http,
voici un exemple de 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>
Depuis que la fonctionnalité deposition: fixeda changé récemment, j'avais du mal à montrer le gif loader au-dessus de tous les éléments, je devais donc utiliser angular's incorporé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
});
})
J'espère que cela t'aides :)
Toutes les réponses sont soit compliquées, soit qu'il soit nécessaire de définir des variables pour chaque requête, ce qui est une pratique très fausse si nous connaissons le concept DRY. Ici, par exemple, un intercepteur simple, je mets la souris sur wait quand ajax commence et le règle sur auto quand ajax se termine.
$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;
}
};
});
Le code doit être ajouté dans l'application config app.config
. J'ai montré comment changer la souris lors du chargement, mais il est possible d'afficher ou de masquer le contenu du chargeur, ou d'ajouter, de supprimer certaines classes CSS qui affichent le chargeur.
Interceptor s'exécutera à chaque appel ajax, il n'est donc pas nécessaire de créer des variables booléennes spéciales ($ scope.loading = true/false, etc.) à chaque appel http.
Voici mon implémentation, aussi simple qu'un ng-show et un compteur de requêtes.
Il utilise un nouveau service pour toutes les demandes adressées à $ 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--;
});
}
} ]);
Et puis vous pouvez utiliser votre base de chargeur sur le nombre d'appels en cours:
<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
.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);
}
}