有效使用角度承诺和延迟

时间:2014-07-25 17:47:26

标签: angularjs

在Angular(以及所有SPA JS框架)中,假设页面导航非常快速且对用户“无缝”。这种速度的唯一瓶颈是使用API​​调用从我们的服务器检索数据。因此,在我们等待我们的API调用获得响应的同时,找到一个我们可以呈现整个页面的解决方案似乎是合理的,除了所呈现的物理数据之外。

我是Angular的新手,我遇到了一些阻碍我实现上述目标和功能的问题。让我带您了解我在学习Angular时遇到的两个问题

问题1:对于Angular

,API响应太慢

起初,我只是在控制器内调用我的API。我有多个数据表,在收到我的回复后会填充。在得到响应之前,我的表有一个简单的<tr>,其中包含一个表示数据加载的.gif“spinner”。我的$scope.gotAPIResponse属性最初设置为false,然后在API响应后,它设置为true

我使用这个布尔值来显示和隐藏“微调器”和表数据。这会导致HTML成功呈现,并且一切对用户来说都很好。但是,仔细查看JavaScript控制台,您可以看到Angular引发了许多错误,因为它在API调用得到响应之前尝试$compile我的模板。错误绝不是一件好事,所以我不得不即兴发挥。

在'LandingController'中

function init() {

            $scope.gotAPIResponse = false;

            // Get Backup data
            API.Backups.getAll(function (data) {
                console.log(data);
                $scope.ActiveCurrentPage = 0;
                $scope.InactiveCurrentPage = 0;
                $scope.pageSize = 25;
                $scope.data = data.result;
                $scope.gotAPIResponse = true;

            });
        }

以下是抛出错误的HTML模板..本机模板和分页指令都会引发错误

<tbody>
     <tr ng-hide="gotAPIResponse"><td colspan="6"><p class="spinner"></p></td></tr>
     <tr ng-if="gotAPIResponse" ng-repeat="active in data.ActiveBackups | paginate:ActiveCurrentPage*pageSize | limitTo:pageSize">
               <td><a ui-sref="order.overview({ orderId: active.ServiceId })" ng-click="select(active)">{{ active.Customer }}</a></td>
               <td>{{ active.LastArchived }}</td>
               <td>{{ active.ArchiveSizeGB | number:2 }}</td>
               <td>{{ active.NumMailboxes }}</td>
               <td>{{ active.ArchivedItems }}</td>
               <td><input type="button" class="backup-restore-btn" value="Restore" /></td>
      </tr>
                </tbody>
            </table>
            <div pagination backup-type="active"></div>

问题2:使用resolve属性阻止所有内容

然后我查看了resolve(ui-router)和$stateProvider(基本角度)中可用的$routeProvider属性。这允许我推迟Controller的实例化,直到收到我的API调用$promise为止。

它的工作原理应该如何,我不再收到任何错误,因为在呈现HTML模板之前API响应可用。但问题是,当您尝试转到该状态或路线时,整个视图将暂时不可用,直到收到$promise为止。这似乎会“滞后”或“阻塞”给用户(看起来很糟糕)

使用resolve属性

 $stateProvider
            .state('dashboard', {
                url: '/',
                controller: 'LandingController',
                resolve: {
                    res: ['API', function(API){
                        return API.Backups.getAll(function (data) {
                            return data.result;
                        }).$promise;
                    }]
                }
            })

然后这个控制器在API调用之后被实例化,但是它之间的时间就像1-2秒......太慢了!

app.controller('LandingController', ['$scope', 'API', 'res',  function ($scope, API, res) {

        $scope.data = res.result;
...

解决方案:正好在

之间

为了解决这个问题,我们需要一个正在我已经尝试过的解决方案之间的解决方案。我们需要尽可能快地实例化控制器,因此可以设置$scope.gotAPIResponse,从而导致“微调器”和DOM的其余部分显示,但是,我们需要推迟HTML模板和其他指令的特定部分这样就不会抛出错误。

人们在生产中使用这个问题是否有效解决方案?

3 个答案:

答案 0 :(得分:4)

更新

最后,这最终成为一个更简单的解决方案。经过一番讨论后,我们稍微重构了一下视图,以避免ng-repeatng-if之前执行而不是所需的行为。

所以不要这样:

<tbody>
     <tr ng-if="!gotAPIResponse">
       <!-- show spinner -->
     </tr>
     <tr ng-if="gotAPIResponse" ng-repeat="stuf in stuffs">
       <!-- lots o' DOM -->
     </tr>
</tbody>

我们将ng-if提升了一级:

<tbody ng-if="!gotAPIResponse">
     <tr >
       <!-- show spinner -->
     </tr>
</tbody>
<tbody ng-if="gotAPIResponse">
     <tr ng-repeat="stuf in stuffs">
       <!-- lots o' DOM -->
     </tr>
</tbody>

这样,每行ng-if都不会被复制,并且会停止进一步处理,直到条件满足为止。


原始答案:

通过使用指令并在$rootScope

上收听一些路线变更事件,这实际上很容易实现

我们的想法是在可用于显示/隐藏某个元素的范围上切换属性。在这种情况下,一个简单的加载......&#34;文本,但在您的情况下,它可以很容易地用于显示微调器。

var routeChangeSpinner = function($rootScope){
 return {
   restrict:'E',
   template:"<h1 ng-if='isLoading'>Loading...</h1>",
   link:function(scope, elem, attrs){
     scope.isLoading = false;

     $rootScope.$on('$routeChangeStart', function(){
       scope.isLoading = true;
     });
     $rootScope.$on('$routeChangeSuccess', function(){
       scope.isLoading = false;
     });
   }
 };
};
routeChangeSpinner.$inject = ['$rootScope'];

app.directive('routeChangeSpinner', routeChangeSpinner);

然后在您的HTML中,您只需执行此操作:

<div class="row">
  <!-- I will show up while a route is changing -->
  <route-change-spinner></route-change-spinner>

  <!-- I will be hidden as long as a route is loading -->
  <div ng-if="!isLoading" class="col-lg-12" ng-view=""></div>
</div>

瞧!在更改路线时显示加载指示器的简单方法。

Full Punker Example

答案 1 :(得分:0)

这是你可以做的(下面的例子):

  1. 将HTML封装在指令
  2. 在控制器中创建数据(从服务器端收到)并将其初始化为空值或初始值
  3. 将此数据传递给您的指令以具有隔离范围
  4. 现在使用$ http服务显式获取控制器中的数据,并在promise的解析中将绑定到指令的数据设置为从服务器检索的数据
  5. 当$ http服务代码在控制器中运行时,该指令将自动更新
  6. 以下是我提供的示例的解释:

    1. 控制器将数据初始化为[1,2],但随后调用函数changedata()调用$ http服务以获取纽约的天气并将其添加到数据阵列。因此,你会看到&#34; Sky is Clear&#34; (或根据您运行此示例的其他内容)在您的指令中呈现。
    2. 这里是plnkr showing directive update with $http

      // index.html
      
      <!DOCTYPE html>
      <html ng-app="plunker">
      
        <head>
          <meta charset="utf-8" />
          <title>AngularJS Plunker</title>
          <script>document.write('<base href="' + document.location + '" />');</script>
          <link rel="stylesheet" href="style.css" />
          <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.20/angular.js" data-semver="1.2.20"></script>
          <script src="app.js"></script>
        </head>
      
        <body ng-controller="MainCtrl">
          <p>Hello {{name}}!</p>
          <input type="button" ng-click="changedata()" value="Change"/>
          <div myview data="model.data"></div>
        </body>
      
      </html>
      
      
      // app.js
      
      var app = angular.module('plunker', []);
      
      app.controller('MainCtrl', function($scope, $http) {
        $scope.name = 'World';
        $scope.model = {
          data: ["1", "2"]
        };
      
        $scope.changedata = function() {
          // updating data explicitly
          $scope.model.data = [4, 5, 6];
      
          url = "http://api.openweathermap.org/data/2.5/weather"
          return $http.get(url, {
            params: {
              q: "New York, NY"
            }
          }).then(function(result) {
            console.log("result=");
            console.log(result);
            if (result !== null) {
              $scope.model.data.push(result.data.weather[0].description);
              return result.data.weather[0].description;
            }
            return "Weather not found";
          });
        };
      
        // updating data implicitly
        $scope.changedata();
      });
      
      app.directive("myview", function() {
      
        return {
          restrict: 'EA',
          template: '<table><tr ng-repeat="d in data"><td>{{d}}</td></tr></table>',
          scope: {
            data: '='
          }
        };
      
      });
      

答案 2 :(得分:0)

模板呈现时遇到错误的第一个问题是因为data在promise解析之后才存在。在启动API调用之前,一个简单的$scope.data = { ActiveBackups:null }应该可以解决这个问题。当angular评估ng-repeat表达式active in data.ActiveBackups时,它将不再尝试引用未定义的属性data

如果您有更长的加载请求,那么您可以通过不使用结算来更好地控制UX。否则,解析倾向于使更简单的控制器代码。