Как сделать индикатор загрузки для каждого асинхронного действия (используя $q) в приложении angularjs
Я знаю, что есть несколько подходов к загрузке индикаторов в angular js (этот, например: https://gist.github.com/maikeldaloo/5140733).
Но они либо должны быть настроены для каждого отдельного вызова, либо - если они действуют глобально, как я хочу - просто применяйте к http-запросам, но не к $q- promises, которые используются в сервисах.
Глобальные показатели загрузки, которые я видел до сих пор, работают с
$httpProvider.responseInterceptors.push(interceptor);
Есть ли что-то похожее для $q
, например, $qProvider.reponseInterceptors
? А если нет, то каким будет наиболее удобный способ реализовать такую функциональность? Можно ли, например, использовать узор-декоратор?
Ответы
Ответ 1
Хотя я считаю это очень сложным, ненужным и, возможно, сломанным, вы можете украсить $q
и переопределить его функцию defer
.
Каждый раз, когда кто-то запрашивает новый defer()
, он запускает вашу собственную версию, которая также увеличивает счетчик. Перед тем, как передать объект defer
, вы зарегистрируете обратный вызов finally
(Angular 1.2.0, но always
тоже может поместиться), чтобы уменьшить счетчик.
Наконец, вы добавляете часы на $rootScope
для наблюдения, когда этот счетчик больше 0 (быстрее, чем pendingPromisses
в $rootScope
и привязывается как ng-show="pendingPromisses > 0"
).
app.config(function($provide) {
$provide.decorator('$q', ['$delegate', '$rootScope', function($delegate, $rootScope) {
var pendingPromisses = 0;
$rootScope.$watch(
function() { return pendingPromisses > 0; },
function(loading) { $rootScope.loading = loading; }
);
var $q = $delegate;
var origDefer = $q.defer;
$q.defer = function() {
var defer = origDefer();
pendingPromisses++;
defer.promise.finally(function() {
pendingPromisses--;
});
return defer;
};
return $q;
}]);
});
Затем представление, связанное с областью, наследующей от $rootScope
, может иметь:
<span ng-show="loading">Loading, please wait</span>
(это не будет работать в директивах с изолированными областями)
Смотрите здесь здесь.
Ответ 2
В официальной документации есть хороший пример для текущей версии 1.2.0.
http://docs.angularjs.org/api/ng.$http (верхняя четверть страницы, поиск перехватчиков)
Мое извлечение этой документации приведет меня к этому решению:
angular.module('RequestInterceptor', [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('requestInterceptor');
})
.factory('requestInterceptor', function ($q, $rootScope) {
$rootScope.pendingRequests = 0;
return {
'request': function (config) {
$rootScope.pendingRequests++;
return config || $q.when(config);
},
'requestError': function(rejection) {
$rootScope.pendingRequests--;
return $q.reject(rejection);
},
'response': function(response) {
$rootScope.pendingRequests--;
return response || $q.when(response);
},
'responseError': function(rejection) {
$rootScope.pendingRequests--;
return $q.reject(rejection);
}
}
});
Затем вы можете использовать pendingRequests > 0 в выражении ng-show.
Ответ 3
После запроса OP это основано на методе, который мы используем для приложения, над которым мы в настоящее время работаем. Этот метод НЕ изменяет поведение $q
, а добавляет очень простой API для обработки promises, для которого требуется какая-то визуальная индикация. Хотя это требует модификации в каждом месте, которое используется, это всего лишь однострочный.
Использование
Существует служба, скажем ajaxIndicator
, которая знает, как обновить часть пользовательского интерфейса. Всякий раз, когда обетоподобный объект должен обеспечивать индикацию до тех пор, пока обещание не будет разрешено, мы используем:
// $http example:
var promise = $http.get(...);
ajaxIndicator.indicate(promise); // <--- this line needs to be added
Если вы не хотите ссылаться на обещание:
// $http example without keeping the reference:
ajaxIndicator.indicate($http.get(...));
Или с ресурсом:
var rc = $resource(...);
...
$scope.obj = rc.get(...);
ajaxIndicator.indicate($scope.obj);
(ПРИМЕЧАНИЕ. Для Angular 1.2 это потребует tweeking, поскольку в объекте ресурса нет $then()
.)
Теперь в корневом шаблоне вам необходимо привязать индикатор к $rootScope.ajaxActive
, например:
<div class="ajax-indicator" ng-show="ajaxActive"></div>
Реализация
(Изменено из нашего источника.) ПРЕДУПРЕЖДЕНИЕ: Эта реализация не учитывает вложенные вызовы! (Наши требования требуют блокировки пользовательского интерфейса, поэтому мы не ожидаем вложенных вызовов, и если я заинтересован, я могу попытаться улучшить этот код.)
app.service("ajaxIndicator", ["$rootScope"], function($rootScope) {
"use strict";
$rootScope.ajaxActive = false;
function indicate(promise) {
if( !$rootScope.ajaxActive ) {
$rootScope.ajaxActive = true;
$rootScope.$broadcast("ajax.active"); // OPTIONAL
if( typeof(promise) === "object" && promise !== null ) {
if( typeof(promise.always) === "function" ) promise.always(finished);
else if( typeof(promise.then) === "function" ) promise.then(finished,finished);
else if( typeof(promise.$then) === "function" ) promise.$then(finished,finished);
}
}
}
function finished() {
$rootScope.ajaxActive = false;
}
return {
indicate: indicate,
finished: finished
};
});
Ответ 4
У меня был один и тот же большой вопрос несколько недель назад, и я решил сделать некоторые директивы для представления состояния загрузки на кнопках действий и загрузке содержимого ng-repeat.
Я просто потратил некоторое время и нажал на github:
https://github.com/ocolot/angularjs_loading_buttons
Надеюсь, это поможет.