在控制器之间共享异步数据而不会发出多个请求

时间:2013-08-22 10:03:05

标签: angularjs angularjs-scope angularjs-service

我正在尝试发出一个$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功能。
  3. 下面你可以看到我的代码:

    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请求,并且将在整个应用程序中跨所有控制器使用响应。

    由于

4 个答案:

答案 0 :(得分:14)

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

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

在解决/拒绝承诺后注册的任何回调将立即使用相同的结果/ failure_reason解决/拒绝。一旦解决/拒绝,承诺就不能改变(其状态)。因此,第一个调用getApps()的控制器将创建承诺。调用getApps()的任何其他控制器将立即获得返回的承诺。

答案 1 :(得分: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 returns array,让上面的代码工作。或者看看数据中有什么,并调整控制器代码。

答案 2 :(得分:1)

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

在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请求。

答案 3 :(得分:0)

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

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

params

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

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

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

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

search.controller.js - 其中搜索控制器

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