example - Mostrando Spinner GIF durante la solicitud de $ http en AngularJS
ng src angular 6 (25)
Estoy usando el servicio $http
de angular para realizar una solicitud ajax.
¿Cómo mostrar un gif del cargador durante la solicitud de ajax?
No veo ningún evento ajaxstartevent
o similar en la documentación.
Acabo de descubrir la directiva de angular-busy
que muestra un pequeño cargador en función de alguna llamada asíncrona.
Por ejemplo, si tiene que hacer un GET
, haga referencia a la promesa en su $scope
,
$scope.req = $http.get(''http://google.fr'');
y llamalo asi
<div cg-busy="req"></div>
Aquí está el GitHub .
También puede instalarlo utilizando bower
(no olvide actualizar las dependencias de su proyecto):
bower install angular-busy --save
Aquí está mi implementación, tan simple como un ng-show y un contador de solicitudes.
Utiliza un nuevo servicio para toda solicitud a $ 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--;
});
}
} ]);
Y luego puede usar su base de carga en el número de llamadas actuales:
<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
Aquí está mi solución que creo que es mucho más fácil que la otra publicada aquí. No estoy seguro de cuán "bonita" es, pero resolvió todos mis problemas
Tengo un estilo css llamado "cargando"
.loading { display: none; }
El html para el div de carga puede ser lo que sea, pero usé algunos iconos de FontAwesome y el método de giro allí:
<div style="text-align:center" ng-class="{ ''loading'': !loading }">
<br />
<h1><i class="fa fa-refresh fa-spin"></i> Loading data</h1>
</div>
En los elementos que desea ocultar simplemente escriba esto:
<something ng-class="{ ''loading'': loading }" class="loading"></something>
y en la función acabo de configurar esto en carga.
(function (angular) {
function MainController($scope) {
$scope.loading = true
Estoy usando SignalR, así que en la función hubProxy.client.allLocks (cuando se hace a través de los bloqueos) pongo
$scope.loading = false
$scope.$apply();
Esto también oculta el {{someField}} cuando la página se está cargando ya que estoy configurando la clase de carga en la carga y AngularJS la elimina después.
Compartí mi versión de la gran respuesta de @Bulltorious, actualizada para versiones angulares más nuevas (utilicé la versión 1.5.8 con este código), y también incorporé la idea de @ JMaylin de usar un contador para ser robusto a múltiples solicitudes simultáneas, y opción para omitir la visualización de la animación para solicitudes que toman menos de un número mínimo de milisegundos:
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;
}
}
}]);
Dado que la funcionalidad de position: fix ha cambiado recientemente, tuve dificultades para mostrar el cargador de gifs por encima de todos los elementos, por lo que tuve que usar el jQuery incorporado de angular.
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 */
Controlador
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
});
})
Espero que esto ayude :)
Esta es una forma sencilla de mostrar un spinner que no requiere una biblioteca de terceros, interceptadores o jQuery.
En el controlador, establecer y restablecer una bandera.
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;
});
};
En el HTML, use la bandera:
<div ng-show="vm.starting">
<img ng-src="spinnerURL" />
</div>
<div ng-hide="vm.starting">
<p>{{vm.data}}</p>
</div>
vm.starting
se establece como true
cuando XHR se inicia y se borra cuando se completa XHR.
Esto funciona bien para mi:
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;
});
Mientras que la promesa devuelta de $ http está pendiente, ng-show evaluará que sea "veraz". Esto se actualiza automáticamente una vez que se resuelve la promesa ... que es exactamente lo que queremos.
Esto realmente depende de su caso de uso específico, pero una forma simple seguiría un patrón como este:
.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;
});
});
Y luego reacciona a ella en tu plantilla:
<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
La siguiente forma tomará nota de todas las solicitudes y se ocultará solo una vez que se hayan completado todas las solicitudes:
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;
}
};
})
Otra solución para mostrar la carga entre diferentes cambios de url es:
$rootScope.$on(''$locationChangeStart'', function() {
$scope.loading++;
});
$rootScope.$on(''$locationChangeSuccess'', function() {
$timeout(function() {
$scope.loading--;
}, 300);
});
Y luego, en el marcado, simplemente alterne la rueda giratoria con ng-show="loading"
.
Si desea mostrarlo en las solicitudes ajax, simplemente agregue $scope.loading++
cuando se inicie la solicitud y cuando finalice agregue $scope.loading--
.
Para cargas de página y modales, la forma más sencilla es usar ng-show direcive y usar una de las variables de datos de alcance. Algo como ng-show = "angular.isUndefined (scope.data.someobject)". Como los datos no están definidos, la rueda giratoria se mostrará. Una vez que el servicio vuelve con los datos y el alcance se completa, la flecha giratoria se ocultará.
Puede utilizar el interceptor angular para gestionar llamadas de solicitud 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>
https://.com/a/49632155/4976786
Puedes probar algo como esto también:
Crear directiva:
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;
}
}
};
});
Luego agregas algo como esto a mainCtrl
// Return TRUE if some request is LOADING, else return FALSE
$scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
Y HTML puede verse así:
<div class="buttons loader">
<span class="icon" loader="isLoading()"></span>
</div>
Si desea mostrar el cargador para cada llamada de solicitud http, entonces puede usar el interceptor angular para administrar las llamadas de solicitud http,
aquí hay un código de muestra
<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>
Si está envolviendo sus llamadas de api dentro de un servicio / fábrica, entonces puede rastrear el contador de carga allí (por answer y excelente sugerencia simultánea de @JMaylin), y consultar el contador de carga mediante una directiva. O cualquier combinación de los mismos.
API WRAPPER
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;
});
DIRECTIVA SPINNER
(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);
USO
<div ng-controller="myCtrl" your-api-spinner> ... </div>
Si está utilizando ngResource, el atributo $ resolvido de un objeto es útil para los cargadores:
Para un recurso de la siguiente manera:
var User = $resource(''/user/:id'', {id:''@id''});
var user = User.get({id: 1})
Puede vincular un cargador al atributo $ resolvido del objeto de recurso:
<div ng-hide="user.$resolved">Loading ...</div>
Todas las respuestas son o son complicadas, o es necesario establecer algunas variables en cada solicitud, lo cual es una práctica muy incorrecta si conocemos el concepto DRY. Aquí, un simple ejemplo de interceptor, puse el mouse en espera cuando se inicia ajax y lo configuro en automático cuando termina ajax.
$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;
}
};
});
El código se debe agregar en la aplicación app.config
. Mostré cómo cambiar el mouse al cargar el estado, pero allí es posible mostrar / ocultar cualquier contenido del cargador, o agregar, eliminar algunas clases css que muestran el cargador.
Interceptor se ejecutará en cada llamada ajax, por lo que no es necesario crear variables booleanas especiales ($ scope.loading = true / false, etc.) en cada llamada http.
crear directiva con este código:
$scope.$watch($http.pendingRequests, toggleLoader);
function toggleLoader(status){
if(status.length){
element.addClass(''active'');
} else {
element.removeClass(''active'');
}
}
https://github.com/wongatech/angular-http-loader es un buen proyecto para esto.
Ejemplo aquí http://wongatech.github.io/angular-http-loader/
El siguiente código muestra un ejemplo de plantilla / loader.tpl.html cuando ocurre una solicitud.
<div ng-http-loader template="example/loader.tpl.html"></div>
Aquí están los encantamientos actuales de AngularJS pasados:
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'']).
//.......
Aquí está el resto (HTML / CSS) .... usando
$(''#mydiv'').show();
$(''#mydiv'').hide();
para cambiarlo. NOTA: lo anterior se usa en el módulo angular al comienzo del post
#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>
Aquí hay una versión que usa una directive
y ng-hide
.
Esto mostrará el cargador durante todas las llamadas a través del servicio $http
angular.
En la plantilla:
<div class="loader" data-loading></div>
directiva:
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'');
}
});
}
};
}]);
Al utilizar la clase ng-hide
en el elemento, puede evitar jquery.
Personalizar: añadir un interceptor
Si crea un interceptor de carga, puede mostrar / ocultar el cargador en función de una condición.
directiva:
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'');
});
};
};
interceptador:
- por ejemplo: no muestres
spinner
cuandoresponse.background === true;
-
request
intercepción y / oresponse
para establecer$rootScope.$broadcast("loader_show");
o$rootScope.$broadcast("loader_hide");
Esta es la forma más fácil de agregar un hilandero, supongo:
Puedes usar ng-show con la etiqueta div de cualquiera de estas hermosas hilanderas http://tobiasahlin.com/spinkit/ {{Esta no es mi página}}
y entonces puedes usar este tipo de lógica
//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
Se utiliza después del interceptor para mostrar la barra de carga en una solicitud 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;
}]);
.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);
}
}
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>
Añade este 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;
}
Y solo en su complemento angular:
$ rootScope.loading = false; $ rootScope.loading = true; -> cuando termina $ http.get.