问题
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:
- Each controller will trigger the
$http
request to obtain the same data already used in another controller; and, - 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