【问题标题】:Keep AngularJS responsive while resolving controller dependencies在解决控制器依赖关系的同时保持 AngularJS 的响应
【发布时间】:2014-01-02 23:52:32
【问题描述】:

AngularJS 在等待任何承诺的控制器依赖解决时停止处理 UI 事件。如果这些依赖项中的任何一个依赖于挂起的 API 调用,应用程序就会变得无响应。

如何设计一个 AngularJS 应用程序,在 ui-router 解析外部控制器依赖项时保持响应?

CoffeeScript 示例:

注入accountBase/accounts/:account_id的路由定义:

$stateProvider.state('accounts',
    abstract: true
    url: '/accounts/:account_id'
    templateUrl: "/views/accounts/index.html"
    resolve:
        accountBase: ($stateParams, $q, Restangular, $rootScope, Session) ->
            deferred = $q.defer()
            base = Restangular.one('accounts', $stateParams.account_id)
            base.get().then (result) ->
                console.log 'resolved accounts', result
                deferred.resolve(result)
            , (error) ->
                deferred.reject('cannot resolve user account')
            return deferred.promise
).state('users', ... # continues

控制器示例

app.controller 'DomainsCtrl', ['$scope', 'accountBase', ($scope, accountBase) ->
    # use resolved accountBase for further processing
    accountBase.get().then ->
        console.log 'do something here'
    return

【问题讨论】:

    标签: angularjs coffeescript dependencies promise angular-ui-router


    【解决方案1】:

    在 AngularJS 中加载数据时,您有很多选择。这里有 3 种方法,它们对数据的呈现和可见性都有不同的影响。

    • 解析(在路由或状态中) - 根据定义,这会暂停页面的呈现,直到所有解析值完成加载。在您的示例中,您返回一个承诺,ui-router 将在开始渲染/执行控制器功能之前解决该承诺。根据我的经验,最好谨慎使用,因为它们会导致不稳定的体验。

    • 在您的控制器中,使用 $resource 并将 get/query 的返回值直接绑定到 $scope。

       var Foo = $resource('/api/foos'); // inject this into your controller
      
       // inside your controller function...
       $scope.foos = Foo.query();
      

      这将导致在 $scope 上放置一个空对象,一旦可用,该对象将被来自 API 调用的数据填充。根据您显示的内容,这可能需要也可能不需要。在您的模板中,您可以检查 $resolved 属性 ng-show="foos.$resolved" 以显示数据正在加载的某种视觉提示。 (另外,我不确定 Restangular 是否提供这种相同类型的“空对象”返回值,或者它是否只返回承诺。)

      值得注意的是:您也可以以相同的方式使用解析。由于将返回一个对象(不是一个承诺,尽管该对象包含一个),渲染/控制器函数的执行将不会被阻止:

      resolve: {
          foos: function(Foo) {
              return Foo.query();
          }
      }
      
    • 为要显示的数据提供一些默认值,然后使用 $http 或 $resource 调用的成功函数来填充实际值。

      $scope.data = {
          stuff: "none yet",
          things: "still waiting"
      };
      
      // when the data is available, replace the defaults.
      Foo.get({id: 1}, function(data) {
          angular.extend($scope.data, data);  
      });    
      

    还有一些值得注意的事情:

    • 缓存数据,以便在用户下次访问该页面/路由时获得一个过时的副本。陈旧的数据通常比没有数据好。您可以使用 angular-cache 将内容存储在本地存储中,以便跨会话使用 - 非常方便。
    • 要将数据保留在当前会话中,请使用 service(它们是单例)来保留对数据的引用。任何注入下面服务的东西都会得到相同的对象实例(在这种情况下是一个数组)。

      angular.factory('ThingsThatProbablyWontChange', function($resource) {
          var Foo = $resource('/api/foos');
      
          return Foo.query();
      });
      

      这是一个简单的示例,但您可以返回一个包装器对象,提供用于操作/重新加载数据的函数。

    希望这会有所帮助。

    更新

    值得一提的是,您的示例代码稍微向后弯曲以加载 accountBase。因为您在 resolve 函数中使用 deferred.resolve(base); 解析了 Promise,所以您的控制器被注入了已经解析的 Promise。为清楚起见,您可以改为使用deferred.resolve(result);,以便将result 的值直接注入到您的控制器中。

    我刚刚查看了Restangular docs,我看到您可以返回promise 的$object 属性以获得由$resource 返回的相同类型的“要填充的空对象”,这将立即解决而不会阻塞渲染等。

    【讨论】:

    • 谢谢。修复了我的示例代码。我通常会解决结果,而不是基础。
    • Np。这解决了你的问题吗?如果是这样,如果您能接受答案,我将不胜感激。如果没有,我很乐意添加细节或其他替代方案。
    猜你喜欢
    • 2015-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-07
    • 2022-10-24
    • 1970-01-01
    相关资源
    最近更新 更多