Подтвердить что ты не робот

Как сделать индикатор загрузки для каждого асинхронного действия (используя $q) в приложении angularjs

Я знаю, что есть несколько подходов к загрузке индикаторов в angular js (этот, например: https://gist.github.com/maikeldaloo/5140733).

Но они либо должны быть настроены для каждого отдельного вызова, либо - если они действуют глобально, как я хочу - просто применяйте к http-запросам, но не к $q- promises, которые используются в сервисах.

Глобальные показатели загрузки, которые я видел до сих пор, работают с

$httpProvider.responseInterceptors.push(interceptor);

Есть ли что-то похожее для $q, например, $qProvider.reponseInterceptors? А если нет, то каким будет наиболее удобный способ реализовать такую ​​функциональность? Можно ли, например, использовать узор-декоратор?

4b9b3361

Ответ 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

Надеюсь, это поможет.