Я использую $http
сервис AngularJS, чтобы сделать запрос Ajax.
Как можно отобразить вращающийся GIF (или другой тип индикатора занятости) во время выполнения Ajax-запроса?
Я не вижу ничего подобного ajaxstartevent
в документации AngularJS.
Я использую $http
сервис AngularJS, чтобы сделать запрос Ajax.
Как можно отобразить вращающийся GIF (или другой тип индикатора занятости) во время выполнения Ajax-запроса?
Я не вижу ничего подобного ajaxstartevent
в документации AngularJS.
Ответы:
Вот текущие прошлые заклинания 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']).
//.......
Вот и все остальное (HTML / CSS) .... используя
$('#mydiv').show();
$('#mydiv').hide();
переключить это. ПРИМЕЧАНИЕ: вышеупомянутое используется в угловом модуле в начале сообщения
#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>
angular.element('#mydiv').show()
вместо$('#mydiv').show()
ng-class
директиву вместо использования JQuery для показа и скрытия элементов?
Это действительно зависит от вашего конкретного случая использования, но простой способ будет следовать такой схеме:
.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;
});
});
А затем отреагируйте на это в вашем шаблоне:
<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
loading
как целое число $scope.loading = 0;
, когда вы начинаете запрос $scope.loading++;
, а когда он заканчивается, вы делаете $scope.loading--;
. И нечего менять в шаблоне. Таким образом, счетчик отображается, когда выполняется хотя бы один запрос, а остальное время
$scope.loading = false
как обратный вызов, так и обратный вызов, состоит в том, чтобы поместить его в .finally()
. Это будет выглядеть так:mySvc.get().then(success, fail).finally(function() { $scope.loading = false; });
Вот версия с использованием directive
и ng-hide
.
Это покажет загрузчик во всех звонках через $http
сервис angular .
В шаблоне:
<div class="loader" data-loading></div>
директива:
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');
}
});
}
};
}]);
используя ng-hide
класс элемента, вы можете избежать jquery.
Настроить: добавить interceptor
Если вы создаете загрузочный перехватчик, вы можете показать / скрыть загрузчик в зависимости от условия.
директива:
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');
});
};
};
перехватчик:
spinner
когдаresponse.background === true;
request
и / или response
установить $rootScope.$broadcast("loader_show");
или$rootScope.$broadcast("loader_hide");
$http
любой сервис.
Если вы используете ngResource, атрибут $ resolved объекта полезен для загрузчиков:
Для ресурса следующим образом:
var User = $resource('/user/:id', {id:'@id'});
var user = User.get({id: 1})
Вы можете связать загрузчик с атрибутом $ resolved объекта ресурса:
<div ng-hide="user.$resolved">Loading ...</div>
https://github.com/wongatech/angular-http-loader - хороший проект для этого.
Пример здесь http://wongatech.github.io/angular-http-loader/
Код ниже показывает пример шаблона / loader.tpl.html, когда происходит запрос.
<div ng-http-loader template="example/loader.tpl.html"></div>
Просто обнаружил angular-busy
директиву, которая показывает маленький загрузчик в зависимости от какого-то асинхронного вызова.
Например, если вам нужно сделать GET
, сослаться на обещание в вашем $scope
,
$scope.req = $http.get('http://google.fr');
и назовите это так:
<div cg-busy="req"></div>
Вот это GitHub.
Вы также можете установить его, используя bower
(не забудьте обновить зависимости вашего проекта):
bower install angular-busy --save
Если вы упаковываете свои вызовы API в сервис / фабрику, то вы можете отследить счетчик загрузки там (за ответ и превосходное одновременное предложение @JMaylin) и ссылаться на счетчик загрузки через директиву. Или любая их комбинация.
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>
Для загрузок и модальных страниц самым простым способом является использование ng-show
директивы и использование одной из переменных данных области. Что-то вроде:
ng-show="angular.isUndefined(scope.data.someObject)".
Здесь пока someObject
не определено, покажет счетчик. Как только служба вернется с данными и someObject
заполнится, счетчик вернется в свое скрытое состояние.
Я думаю, это самый простой способ добавить спиннер:
Вы можете использовать ng-show с тегом div любого из этих прекрасных блесен http://tobiasahlin.com/spinkit/ {{Это не моя страница}}
и тогда вы можете использовать этот вид логики
//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>
Добавьте эту 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;
}
И просто в ваш угловой добавить:
$ rootScope.loading = false; $ rootScope.loading = true; -> когда заканчивается $ http.get.
Поделиться моей версией отличного ответа от @bulltorious, обновленной для более новых угловых сборок (я использовал версию 1.5.8 с этим кодом), а также включил идею @ JMaylin об использовании счетчика, чтобы быть устойчивым к нескольким одновременным запросам, и возможность пропустить показ анимации для запросов, занимающих менее минимального количества миллисекунд:
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;
}
}
}]);
Вы можете использовать угловой перехватчик для управления запросами 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>
Это простой способ показать спиннер, который не требует сторонней библиотеки, перехватчиков или jQuery.
В контроллере установите и сбросьте флаг.
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;
});
};
В HTML используйте флаг:
<div ng-show="vm.starting">
<img ng-src="spinnerURL" />
</div>
<div ng-hide="vm.starting">
<p>{{vm.data}}</p>
</div>
vm.starting
Флаг устанавливается , true
когда XHR начинается и очищается , когда XHR не завершится.
Это хорошо работает для меня:
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>
Угловой:
$scope.req = $http.get("/admin/view/"+id).success(function(data) {
$scope.data = data;
});
Пока обещание, возвращаемое из $ http, еще не выполнено, ng-show оценит его как «правдивое». Это автоматически обновляется, как только обещание будет выполнено ... это именно то, что мы хотим.
Используется следующий перехватчик для отображения строки загрузки по запросу 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);
}
}
создать директиву с этим кодом:
$scope.$watch($http.pendingRequests, toggleLoader);
function toggleLoader(status){
if(status.length){
element.addClass('active');
} else {
element.removeClass('active');
}
}
Другое решение для отображения загрузки между различными изменениями URL:
$rootScope.$on('$locationChangeStart', function() {
$scope.loading++;
});
$rootScope.$on('$locationChangeSuccess', function() {
$timeout(function() {
$scope.loading--;
}, 300);
});
А затем в разметке просто переключите спиннер с помощью ng-show="loading"
.
Если вы хотите отобразить его в запросах ajax, просто добавьте, $scope.loading++
когда запрос начинается и когда он заканчивается, добавьте $scope.loading--
.
Вы можете попробовать что-то вроде этого:
Создать директиву:
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;
}
}
};
});
Затем вы добавляете что-то вроде этого в mainCtrl
// Return TRUE if some request is LOADING, else return FALSE
$scope.isLoading = function() {
return $http.pendingRequests.length > 0;
};
И HTML может выглядеть так:
<div class="buttons loader">
<span class="icon" loader="isLoading()"></span>
</div>
Следующий способ будет принимать к сведению все запросы и скрывать только после выполнения всех запросов:
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;
}
};
})
Поскольку функциональность position: fixed недавно изменилась, мне было трудно показать gif-загрузчик над всеми элементами, поэтому мне пришлось использовать встроенный в Angular 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 */
контроллер
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
});
})
Надеюсь это поможет :)
Все ответы являются либо сложными, либо должны устанавливать некоторые переменные для каждого запроса, что является очень неправильной практикой, если мы знаем концепцию СУХОЙ. Вот простой пример перехватчика, я установил мышь на ожидание при запуске ajax и установил его в автоматический режим при завершении 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;
}
};
});
Код должен быть добавлен в конфиг приложения app.config
. Я показал, как изменить мышь при загрузке, но там можно показать / скрыть любой контент загрузчика, или добавить, удалить некоторые классы CSS, которые показывают загрузчик.
Перехватчик будет запускаться при каждом вызове ajax, поэтому нет необходимости создавать специальные логические переменные ($ scope.loading = true / false и т. Д.) При каждом вызове http.
Вот моя реализация, такая же простая, как ng-show и счетчик запросов.
Он использует новый сервис для всех запросов к $ 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--;
});
}
} ]);
И тогда вы можете использовать базу загрузчика по количеству текущих вызовов:
<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
если вы хотите показывать загрузчик для каждого вызова http-запроса, то вы можете использовать угловой перехватчик для управления вызовами http-запроса,
вот пример кода
<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>
Не нужно использовать директиву, не нужно усложнять.
Вот код, который нужно поместить рядом с кнопкой отправки или где вы хотите, чтобы спиннер был:
<span ng-show="dataIsLoading">
<img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" style="height:20px;"/>
</span>
И тогда в вашем контроллере:
$scope.dataIsLoading = true
let url = '/whatever_Your_URL_Is'
$http.get(url)
.then(function(response) {
$scope.dataIsLoading = false
})
Вот мое решение, которое я чувствую намного легче, чем другие, размещенные здесь. Не уверен, насколько это "красиво", но это решило все мои проблемы
У меня есть стиль CSS под названием «загрузка»
.loading { display: none; }
HTML-код для загрузки div может быть любым, но я использовал некоторые значки FontAwesome и метод spin там:
<div style="text-align:center" ng-class="{ 'loading': !loading }">
<br />
<h1><i class="fa fa-refresh fa-spin"></i> Loading data</h1>
</div>
На элементах, которые вы хотите скрыть, просто напишите это:
<something ng-class="{ 'loading': loading }" class="loading"></something>
и в функции я просто установить это под нагрузкой.
(function (angular) {
function MainController($scope) {
$scope.loading = true
Я использую SignalR, поэтому в функции hubProxy.client.allLocks (когда она будет проходить через блокировки), я просто положил
$scope.loading = false
$scope.$apply();
Это также скрывает {{someField}} при загрузке страницы, так как я устанавливаю класс загрузки под нагрузкой, а AngularJS удаляет его впоследствии.