在AngularJS中的$http请求期间显示旋转动画?

236

我正在使用AngularJS的$http服务进行Ajax请求。

在执行Ajax请求时,如何显示旋转GIF(或其他类型的忙指示器)?

我在AngularJS文档中没有看到类似于ajaxstartevent的东西。


2
如果您想要一个基于HTTP拦截器的简单旋转器,我有一个Angular模块可供使用。它使用了流行的Identified Sham旋转器。请看这里:https://github.com/harinair/angular-sham-spinner - Hari Gangadharan
1
我写了一个插件angular-httpshooter,它在调用请求之前释放带有配置数据的事件,并在接收到响应后释放另一个事件,您可以编写全局加载器来捕获这些事件。 - Siddharth
27个回答

472
这真的取决于您具体的使用情况,但一个简单的方法可以按照以下模式进行:
.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>

225
如果您同时有多个ajax请求,您可以将“loading”声明为一个整数$scope.loading = 0;,当请求开始时,执行$scope.loading ++;,当请求结束时,执行$scope.loading--;。在模板中没有任何需要更改的内容。 因此,当至少存在一个正在进行的请求时,会显示旋转器,否则会隐藏。 - JMaylin
16
在错误回退函数中,你可能也应该将 loading 设置为 false。 - sebnukem
3
@sebnukem 没错,你说得对。这个例子比较高级,但为了更清楚,我还是加上了。感谢你的反馈! - Josh David Miller
1
@JMaylin - 你可以在 $scope.loading 上使用 $watch 来执行其他操作。 - Jason
5
一种更清晰的方式是,在成功和失败的回调中都不需要放置 $scope.loading = false,而是将它放在 .finally() 中。代码如下所示:mySvc.get().then(success, fail).finally(function() { $scope.loading = false; }); - Tom
显示剩余8条评论

88

以下是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(); 

切换它。注意:上述内容用于文章开头的Angular模块。

#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>

19
请注意,您可以使用 angular.element('#mydiv').show() 而不是 $('#mydiv').show()。该语句意为:在代码中显示 id 为“mydiv”的元素,前者使用了 Angular 的 element 方法来获取 DOM 元素,后者使用了 jQuery 库的 $ 符号来完成相同的操作。 - JMaylin
8
如果您的页面发起了多个 Ajax 请求,这种方法就不起作用了。它会在第一个请求结束后隐藏加载动画。 - Meeker
38
根据AngularJS的最佳实践(被接受的解决方案违反了这些实践),在指令外部不应该修改DOM。考虑从指令内部调用元素的show/hide方法。 - Mariusz
7
我不确定我是否了解足够去发表评论...但是做这件事的正确方法不应该是使用 ng-class 指令来代替使用 JQuery 显示和隐藏元素吗? - James Heald
3
@JamesHeald是正确的,你在Angular中基本上不需要使用jQuery进行任何dom操作。请查看:ng-class,ng-if,ng-hide,ng-show等... 几乎所有你所需的功能都有指令可以实现。 - jKlaus
显示剩余5条评论

46

以下是使用directiveng-hide的版本。

这将在通过Angular的$http服务进行所有调用时显示加载器。

在模板中:

<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。

自定义:添加一个拦截器

如果创建一个加载拦截器,您可以基于某个条件来显示/隐藏加载器。

指令:

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');
      });
  };
};

拦截器:

  • 例如:当response.background === true;时不显示spinner
  • 拦截request和/或response以设置$rootScope.$broadcast("loader_show");$rootScope.$broadcast("loader_hide");

有关编写拦截器的更多信息


@punkrockpolly 在我的具体情况下,我有两个单独的组件调用一个服务。但这只适用于其中一个组件,我应该传递一个参数或者做些什么使其可重用呢? - napstercake
@razorbladeпјЊжЇЏеЅ“ж‚ЁйЂљиї‡д»»дЅ•жњЌеЉЎдЅїз”Ё$httpиї›иЎЊи°ѓз”Ёж—¶пјЊе®ѓйѓЅдјљи‡ЄеЉЁиїђиЎЊгЂ‚ - punkrockpolly

31

如果您正在使用ngResource,对象的$resolved属性对于加载器非常有用:

对于以下资源:

var User = $resource('/user/:id', {id:'@id'});
var user = User.get({id: 1})
您可以将加载器链接到资源对象的 $resolved 属性:
<div ng-hide="user.$resolved">Loading ...</div>

14

13

我刚刚发现了angular-busy指令,它会根据某些异步调用显示一个小的加载器。

例如,如果你需要发起一个GET请求,在你的$scope中引用这个promise对象,

$scope.req = $http.get('http://google.fr');

并按如下方式进行调用:
<div cg-busy="req"></div>

这里是GitHub链接。

您还可以使用bower进行安装(不要忘记更新项目依赖):

bower install angular-busy --save

我在文档中找不到“禁用某些元素”的方法。你能解释一下吗?例如,我想在显示旋转器时禁用按钮。 - c4k
angular-busy 只能在你的元素上放置一个遮罩,我认为你不能像你想要的那样禁用一个按钮,但是你可以将背景设置为空模板,以给出这种印象。我不是母语人士,对于造成的混淆表示抱歉 :) - Preview
好的,我修改了你的答案以防止其他人产生相同的困惑。一个讲法语的英语人对另一个讲法语的人总是令人困惑的 :) - c4k

5

如果您正在服务/工厂中包装api调用,则可以在那里跟踪加载计数器(根据答案和@JMaylin的出色同时建议),并通过指令引用加载计数器,或任何组合。

API包装器

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;
    });

SPINNER DIRECTIVE

(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>

5

对于页面加载和模态框,最简单的方法是使用ng-show指令,并使用其中一个作用域数据变量。例如:

ng-show="angular.isUndefined(scope.data.someObject)".

这里,当someObject未定义时,旋转图标将显示。一旦服务返回数据并填充了someObject,旋转图标将返回到隐藏状态。


3
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;
}

在你的Angular中添加以下内容:

$rootScope.loading = false; 当 $http.get 结束时,$rootScope.loading = true;


3

这应该是添加Spinner的最简单方法:

您可以使用ng-show及任何一个美丽的Spinner的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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接