使用AngularJS显示慢速脚本的加载动画?

时间:2014-04-11 03:48:48

标签: javascript angularjs

在angularjs 1.2中,过滤ng-repeat多行(> 2,000行)的操作可能会变得很慢(> 1秒)。
我知道我可以使用limitTo,分页,自定义过滤器等来优化执行时间。但我仍然有兴趣知道在浏览器忙于运行长脚本时是否可以显示加载动画。

在有角度的情况下,我认为只要$digest运行就可以调用,因为这似乎是占用大部分时间的主函数,可能会多次调用。

related question中,没有给出有用的答案。任何帮助非常感谢!

8 个答案:

答案 0 :(得分:20)

问题是只要Javascript正在执行,UI就没有机会更新。即使您在过滤之前呈现微调器,只要Angular忙,它就会显示为“冻结”。

解决此问题的一种方法是过滤块,如果有更多数据可用,请在小$timeout之后再次过滤。超时使UI线程有机会运行并显示更改和动画。

证明这一原则的小提琴是here

它不使用Angular的过滤器(它们是同步的)。相反,它使用以下函数过滤data数组:

function filter() {
    var i=0, filtered = [];
    innerFilter();

    function innerFilter() {
        var counter;
        for( counter=0; i < $scope.data.length && counter < 5; counter++, i++ ) {
            /////////////////////////////////////////////////////////
            // REAL FILTER LOGIC; BETTER SPLIT TO ANOTHER FUNCTION //
            if( $scope.data[i].indexOf($scope.filter) >= 0 ) {
                filtered.push($scope.data[i]);
            }
            /////////////////////////////////////////////////////////
        }
        if( i === $scope.data.length ) {
            $scope.filteredData = filtered;
            $scope.filtering = false;
        }
        else {
            $timeout(innerFilter, 10);
        }
    }
}

当过滤器处于活动状态时,它需要2个支持变量:$scope.filteringtrue,这样我们就有机会显示微调器并禁用输入; $scope.filteredData会收到过滤器的结果。

有3个隐藏参数:

  • 块大小(counter < 5)的目的很小,以证明效果
  • 超时延迟($timeout(innerFilter, 10))应该很小,但足以让UI线程有时间做出响应
  • 实际的过滤逻辑,应该是现实生活场景中的回调。

这只是概念的证明;我建议重构它(可能是指令)以供实际使用。

答案 1 :(得分:7)

以下是步骤:

  1. 首先,您应该使用CSS动画。没有JS 驱动动画和GIF应该在繁重的过程中使用。单线程限制。动画将冻结。 CSS动画与UI线程分开,IE 10+和所有主流浏览器都支持它们。
  2. 写一个指令并将其放在ng-view之外 fixed定位。
  3. 使用一些特殊标志将其绑定到您的app控制器。
  4. 在长/重进程之前和之后切换此指令的可见性。 (您甚至可以将文本消息绑定到指令以显示一些 对用户有用的信息)。 - 在一个繁重的过程循环中直接与这个或任何其他东西进行交互将花费更长的时间来完成。这对用户不利!
  5. 指令模板:

    <div class="activity-box" ng-show="!!message">
        <img src="img/load.png" width="40" height="40" />
        <span>{{ message }}</span>
    </div>
    

    activity指令:

    具有单个属性message的简单指令。请注意上面模板中的ng-show指令。 message既可用于切换活动指示符,也可用于设置用户的信息文本。

    app.directive('activity', [
        function () {
            return {
                restrict: 'EA',
                templateUrl: '/templates/activity.html',
                replace: true,
                scope: {
                    message: '@'
                },
                link: function (scope, element, attrs) {}
            };
        }
    ]);
    

    SPA HTML:

    <body ng-controller="appController">
        <div ng-view id="content-view">...</div>
        <div activity message="{{ activityMessage }}"></div>
    </body>
    

    请注意,activity指令位于ng-view之外。它将在您的单页面应用程序的每个部分提供。

    APP控制器:

    app.controller('appController',
        function ($scope, $timeout) {
            // You would better place these two methods below, inside a 
            // service or factory; so you can inject that service anywhere
            // within the app and toggle the activity indicator on/off where needed
            $scope.showActivity = function (msg) {
                $timeout(function () {
                    $scope.activityMessage = msg;
                });
            };
            $scope.hideActivity = function () {
                $timeout(function () {
                    $scope.activityMessage = '';
                }, 1000); // message will be visible at least 1 sec
            };
            // So here is how we do it:
            // "Before" the process, we set the message and the activity indicator is shown
            $scope.showActivity('Loading items...');
            var i;
            for (i = 0; i < 10000; i += 1) {
                // here goes some heavy process
            }
            // "After" the process completes, we hide the activity indicator.
            $scope.hideActivity();
        });
    

    当然,你也可以在其他地方使用它。例如您可以在承诺结算时致电$scope.hideActivity();。或者在request的{​​{1}}和response上切换活动也是一个好主意。

    CSS示例:

    httpInterceptor

    希望这有帮助。

答案 2 :(得分:5)

使用spin.js,网站http://fgnass.github.com/spin.js/显示非常简单的步骤。 加载动画在CSS中,与UI线程分开,因此可以顺利加载。

答案 3 :(得分:2)

您可以做的是检测ngRepeat的结尾为this post所说的。

我会在控制器中做类似的事情:

$scope.READY = false;

在指令中,正如上面的帖子所说,我会做类似的事情:

if (scope.$last) {
    $scope.READY = true;
}

你可以拥有一个基于CSS的加载器/微调器

<div class="loader" ng-show="!READY">
</div>

当然,你也可以拥有基于CSS的动画,这些动画独立于js执行。

答案 4 :(得分:1)

您可以使用WebWorkers在另一个线程中运行过滤器,因此您的angularjs页面不会阻止。

如果您不使用网络工作者,浏览器可能会获得javascript执行超时并完全停止您的角度应用程序,即使您没有得到任何超时,您的应用程序也会冻结,直到计算结束。

更新23.04.14

我看到使用scalyrbindonce的大型项目取得了重大的绩效改进

答案 5 :(得分:1)

以下是一个工作示例: -

angular
  .module("APP", [])
  .controller("myCtrl", function ($scope, $timeout) {
    var mc = this
    
    mc.loading = true
    mc.listRendered = []
    mc.listByCategory = []
    mc.categories = ["law", "science", "chemistry", "physics"]
    
    
    mc.filterByCategory = function (category) {
      mc.loading = true
      // This timeout will start on the next event loop so 
      // filterByCategory function will exit just triggering 
      // the show of Loading... status

      // When the function inside timeout is called, it will
      // filter and set the model values and when finished call 
      // an inbuilt $digest at the end.
      $timeout(function () {
        mc.listByCategory = mc.listFetched.filter(function (ele) {
          return ele.category === category          
        })
        mc.listRendered = mc.listByCategory
        $scope.$emit("loaded")
      }, 50)
    }
    
    // This timeout is replicating the data fetch from a server
    $timeout(function () {
      mc.listFetched = makeList()
      mc.listRendered = mc.listFetched
      mc.loading = false    
    }, 50)
    
    $scope.$on("loaded", function () { mc.loading = false })
  })
  
  function makeList() {
    var list = [
      {name: "book1", category: "law"},
      {name: "book2", category: "science"},
      {name: "book1", category: "chemistry"},
      {name: "book1", category: "physics"}      
    ]
    var bigList = []
    for (var i = 0; i <= 5000; i++) {
      bigList = bigList.concat(list)
    }
    return bigList
  }
button {
  display: inline-block;      
}
<html>
  <head>
    <title>This is an Angular Js Filter Workaround!!</title>
  </head>
   
  <body ng-app="APP">
    
    <div ng-controller="myCtrl as mc">
      <div class = "buttons">
        <label>Select Category:- </label>
        <button ng-repeat="category in mc.categories" ng-click="mc.filterByCategory(category)">{{category}}</button>
      </div>
      
      <h1 ng-if="mc.loading">Loading...</h1>
      
      <ul ng-if="!mc.loading">
        <li ng-repeat="ele in mc.listRendered track by $index">{{ele.name}} - {{ele.category}}</li>
      </ul>
    </div>
    
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
    
  </body>
  
<html>

答案 6 :(得分:0)

在这种情况下可以使用Promise / deferred,您可以调用notify来查看代码的进度,来自angular.js的文档:https://code.angularjs.org/1.2.16/docs/api/ng/service/ $ q 这是一个关于使用ng-Promise的重JS处理的教程:http://liamkaufman.com/blog/2013/09/09/using-angularjs-promises/,希望它有用。

// app工厂用于保存数据模型

app.factory('postFactory',function($ http,$ q,$ timeout){

var  factory = {

    posts : false,

    getPosts : function(){

        var deferred = $q.defer();

        //avoiding the http.get request each time 
        //we call the getPosts function

        if (factory.posts !== false){
            deferred.resolve(factory.posts);

        }else{

            $http.get('posts.json')
            .success(function(data, status){
                factory.posts = data

                //to show the loading !
                $timeout(function(){
                    deferred.resolve(factory.posts)
                }, 2000);

            })
            .error(function(data, status){
                deferred.error('Cant get the posts !')
            })

        };  

        return deferred.promise;
    },

    getPost : function(id){

        //promise
        var deferred = $q.defer();

        var post = {};
        var posts = factory.getPosts().then(function(posts){
            post = factory.posts[id];

            //send the post if promise kept
            deferred.resolve(post);

        }, function(msg){
            deferred.reject(msg);
        })

        return deferred.promise;
    },

};
return factory;

});

答案 7 :(得分:0)

您可以使用此网址中的代码: http://www.directiv.es/angular-loading-bar

你也可以找到一个工作演示。

这是代码:

    <!DOCTYPE html>
<html ng-app="APP">
<head>
    <meta charset="UTF-8">
    <title>angular-loading-bar example</title>
    <link rel="stylesheet" type="text/css" href="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.css"/>
    <style>
        body{
            padding:25px;
        }
    </style>
</head>
<body ng-controller="ExampleController">
    <button id="reloader" ng-click="getUsers()">Reload</button>
    <ul ng-repeat="person in data">
        <li>{{person.lname}}, {{person.fname}}</li> 
    </ul>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular-animate.min.js"></script>
    <script src="/application/html/js/chieffancypants/angular-loading-bar/loading-bar.min.js"></script>
    <script>
    angular.module('APP', ['chieffancypants.loadingBar', 'ngAnimate']).
    controller("ExampleController",['$scope','$http',function($scope,$http){
        $scope.getUsers = function(){
            $scope.data=[];
            var url = "http://www.filltext.com/?rows=10&fname={firstName}&lname={lastName}&delay=3&callback=JSON_CALLBACK"
            $http.jsonp(url).success(function(data){
                $scope.data=data;
            })
        }
        $scope.getUsers()

    }])
    </script>
</body>
</html>

我该如何使用它?

通过npm或bower

安装
$ npm install angular-loading-bar
$ bower install angular-loading-bar

要使用,只需将其作为依赖项添加到您的应用中即可完成!

angular.module('myApp', ['angular-loading-bar'])