【问题标题】:Share async data between controllers without making multiple requests在控制器之间共享异步数据而不发出多个请求
【发布时间】:2013-08-25 00:01:23
【问题描述】:

我正在尝试发出一个 $http 请求以获取我的一个 JSON 文件并在我的所有控制器中使用数据。

我在 egghead.io 上看到了如何在多个控制器之间共享数据,并且我还阅读了这个 StackOverflow 问题:“Sharing a variable between controllers in angular.js”。

但是,那里的答案不使用$http 模块。使用$http 时,控制器没有可处理的数据,等到收到响应时已经为时已晚。

然后我在 StackOverflow 上找到了方法 $q.defer 和这个问题:“AngularJS share asynchronous service data between controllers

那里发布的解决方案运行良好,但有两个问题:

  1. 每个控制器都会触发$http请求来获取已经在另一个控制器中使用的相同数据;并且,
  2. 如果我尝试操纵收到的数据,我有一个 then 函数。

下面你可以看到我的代码:

controllers.js

'use strict';

/* Controllers */

function appInstallerListCtrl($scope, Data) {
  $scope.apps = Data;
}

function appInstallerDetailCtrl($scope, $routeParams, Data) {
  $scope.appId = $routeParams.appId;
  $scope.apps = Data;  
  console.log($scope.apps); // <-- then function
  console.log(Data); // <-- then function with $vv data returned but I can't access it

  for (var i in $scope.apps) // <--- no way, baby!
    console.log(i);
}

app.js

var app = angular.module('appInstaller', []);

app.factory('Data', function($http, $q) {
  var defer = $q.defer();
  $http.get('apps.json').then(function(result) {
    defer.resolve(result.data.versions.version);
  });
  return defer.promise;
});

app.config(['$routeProvider', function($routeProvider) {
  $routeProvider.
    when('/app', {templateUrl: 'partials/app-list.html',   controller: appInstallerListCtrl}).
    when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
    otherwise({redirectTo: '/app'});
}]);

我想要的是,在启动应用程序时,将执行 $http 请求,并且响应将在整个应用程序的所有控制器中使用。

谢谢

【问题讨论】:

    标签: angularjs angularjs-scope angularjs-service


    【解决方案1】:

    由于您使用的是 Promise,因此要访问 Promise 返回的数据,请使用回调语法

    function appInstallerDetailCtrl($scope, $routeParams, Data) {
      $scope.appId = $routeParams.appId;
       Data.then(function(returnedData) {
            $scope.apps=returnedData;
            console.log($scope.apps);
            for (var i in $scope.apps)
               console.log(i)   
       });   
    }
    

    确保这一点

    defer.resolve(result.data.versions.version);
    

    resolve 返回数组,以使上述代码正常工作。或者查看数据中的内容并调整控制器代码。

    【讨论】:

    • 谢谢,但代码不正确。我收到一个错误:Uncaught SyntaxError: Unexpected token { after the Data.then 并且还有 ) 括号。
    • docs.angularjs.org/api/ng.$q 上查看 $q 上的文档,我只是强调了基本思想。我也尝试过修复我的代码。
    • 哇它好用!非常感谢,我会查看您突出显示的文档。
    【解决方案2】:

    我喜欢将我的数据存储在服务中,并向控制器返回一个承诺,因为通常你需要处理那里的任何错误。

    app.factory('Data', function($http, $q) {
       var data = [],
           lastRequestFailed = true,
           promise;
       return {
          getApps: function() {
             if(!promise || lastRequestFailed) {
                // $http returns a promise, so we don't need to create one with $q
                promise = $http.get('apps.json')
                .then(function(res) {
                    lastRequestFailed = false;
                    data = res.data;
                    return data;
                }, function(res) {
                    return $q.reject(res);
                });
             }
             return promise;
          }
       }
    });
    
    .controller('appInstallerListCtrl', ['$scope','Data',
    function($scope, Data) {
        Data.getApps()
        .then(function(data) {
            $scope.data = data;
        }, function(res) {
            if(res.status === 500) {
                // server error, alert user somehow
            } else { 
                // probably deal with these errors differently
            }
        });
    }]);
    

    在 promise 被解析/拒绝后注册的任何回调都将立即以相同的 result/failure_reason 被解析/拒绝。一旦解决/拒绝,promise 就不能改变(它的状态)。所以第一个调用getApps() 的控制器将创建promise。任何其他调用 getApps() 的控制器将立即获得返回的承诺。

    【讨论】:

    • +1,这是(我认为)同步大型应用程序的最佳实践。谢谢,并且经常在stackoverflow上,你的权利:)
    • 绑定到承诺在较新的版本中不起作用(我认为是 1.2+)。使用 Data.getApps().then(function(result){//将结果保存在 $scope})
    • @shrutyzet,谢谢。我终于有时间更新我的答案了。
    • 但是如果你运行 Data.getApps().then(function(data) { .. });多次,您会收到多个 GET 请求。如何坚持一个请求?
    • @Anze,我明白了。如果您将 if 语句更改为 if(!promise),那么您只会收到一个 GET 请求。似乎使用两个控制器,promise 在设置 lastRequestFailed 之前得到解决,因此这引入了竞争条件。
    【解决方案3】:

    我发现不确定天气的方式是不是最好的方法。

    在 HTML 中

    <body ng-app="myApp">
      <div ng-controller="ctrl">{{user.title}}</div>
      <hr>
      <div ng-controller="ctrl2">{{user.title}}</div>
    </body>
    

    在 Javascript 中

     var app = angular.module('myApp', []);
       app.controller('ctrl', function($scope, $http, userService) {
          userService.getUser().then(function(user) {
            $scope.user = user;
          });
        });
    
       app.controller('ctrl2', function($scope, $http, userService) {
          userService.getUser().then(function(user) {
            $scope.user = user;
          });
        });
    
       app.factory('userService', function($http, $q) {
        var promise;
        var deferred = $q.defer();
          return {
            getUser: function() {
              if(!promise){     
              promise = $http({
                  method: "GET",
                  url: "https://jsonplaceholder.typicode.com/posts/1"
                }).success(function(res) {
                    data = res.data;
                  deferred.resolve(res);
                })
                .error(function(err, status) {
                  deferred.reject(err)
                });
              return deferred.promise;
              }
              return deferred.promise;
            }
          }
        });
    

    这只会发出 1 个 HTTP 请求。

    【讨论】:

      【解决方案4】:

      我的问题是我不想在加载另一个控制器之前等待resolve,因为如果网络很慢,它会显示控制器之间的“滞后”。我的工作解决方案是通过ui-router's params 在控制器之间传递承诺,并且可以在第二个控制器中异步加载来自承诺的数据:

      app.route.js - 设置传递给 SearchController 的可用参数,显示搜索结果

              .state('search', {
                  url: '/search',
                  templateUrl: baseDir + 'search/templates/index.html',
                  controller: 'SearchController',
                  params: {
                      searchPromise: null
                  }
              })
      

      landing.controller.js - 用户添加搜索输入并提交的控制器

          let promise = SearchService.search(form);
          $state.go('search', {
              searchPromise: promise
          });
      

      search.service.js - 从用户输入返回承诺的服务

          function search(params) {
              return new Promise(function (resolve, reject) {
                  $timeout(function() {
                      resolve([]) // mimic a slow query but illustrates a point
                  }, 3000)
              })
          }
      

      search.controller.js - 在哪里搜索控制器

          let promise = $state.params.searchPromise;
      
          promise.then(r => {
              console.log('search result',r);
          })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-15
        • 2014-03-22
        • 1970-01-01
        • 2020-06-16
        • 1970-01-01
        • 2013-05-28
        • 2016-11-30
        • 1970-01-01
        相关资源
        最近更新 更多