Share async data between controllers without making multiple requests

不羁岁月 提交于 2019-12-12 07:49:08

问题


I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.

I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".

However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.

I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"

The solution posted there works fine, BUT it has two issues:

  1. Each controller will trigger the $http request to obtain the same data already used in another controller; and,
  2. If I try to manipulate the data received I have a then function.

Below you can see my code:

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

What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.

Thanks


回答1:


Since you are using a promise, to access the data returned by promise use the callback syntax

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

Make sure this

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

resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.




回答2:


I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.

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

Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.




回答3:


I found the way not sure weather it is a best approach to do it or not.

In HTML

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

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

This will exactly make only 1 HTTP request.




回答4:


My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:

app.route.js - setting the available params to be passed to SearchController, which shows the search results

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

landing.controller.js - controller where the user adds search input and submits

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

search.service.js - a service that returns a promise from the user input

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

search.controller.js - where search controller

    let promise = $state.params.searchPromise;

    promise.then(r => {
        console.log('search result',r);
    })


来源:https://stackoverflow.com/questions/18377348/share-async-data-between-controllers-without-making-multiple-requests

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!