Обработка ответа HTTP в сервисе
Недавно я опубликовал подробное описание проблемы, с которой я столкнулся здесь в SO. Поскольку я не мог отправить фактический запрос $http
, я использовал таймаут для имитации асинхронного поведения. Связывание данных с моей модели для просмотра работает корректно, с помощью @Gloopy
Теперь, когда я использую $http
вместо $timeout
(проверено локально), я мог видеть, что асинхронный запрос был успешным, и data
заполняется json-ответом в моей службе. Но, мой взгляд не обновляется.
обновлено Plunkr здесь
Ответы
Ответ 1
Вот Plunk, который делает то, что вы хотите: http://plnkr.co/edit/TTlbSv?p=preview
Идея заключается в том, что вы напрямую работаете с promises и своими функциями "then", чтобы манипулировать и получать доступ к асинхронно возвращенным ответам.
app.factory('myService', function($http) {
var myService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
Вот несколько более сложная версия, которая кэширует запрос, поэтому вы делаете это только в первый раз (http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview):
app.factory('myService', function($http) {
var promise;
var myService = {
async: function() {
if ( !promise ) {
// $http returns a promise, which has a then function, which also returns a promise
promise = $http.get('test.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
}
// Return the promise to the controller
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = {};
};
$scope.getData = function() {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
};
});
Ответ 2
Пусть это будет просто. Это так же просто, как
- Верните
promise
в свою службу (нет необходимости использовать then
в службе)
- Используйте
then
в вашем контроллере
Demo. http://plnkr.co/edit/cbdG5p?p=preview
var app = angular.module('plunker', []);
app.factory('myService', function($http) {
return {
async: function() {
return $http.get('test.json'); //1. this returns promise
}
};
});
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function(d) { //2. so you can use .then()
$scope.data = d;
});
});
Ответ 3
Поскольку он является асинхронным, $scope
получает данные перед завершением вызова ajax.
Вы можете использовать $q
в своей службе для создания promise
и вернуть его обратно
контроллер и контроллер получают результат в then()
вызове с promise
.
В вашем сервисе
app.factory('myService', function($http, $q) {
var deffered = $q.defer();
var data = [];
var myService = {};
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
console.log(d);
deffered.resolve();
});
return deffered.promise;
};
myService.data = function() { return data; };
return myService;
});
Затем в вашем контроллере:
app.controller('MainCtrl', function( myService,$scope) {
myService.async().then(function() {
$scope.data = myService.data();
});
});
Ответ 4
tosh shimayama имеет решение, но вы можете упростить многое, если используете тот факт, что $http возвращает promises и что promises может вернуть значение:
app.factory('myService', function($http, $q) {
myService.async = function() {
return $http.get('test.json')
.then(function (response) {
var data = reponse.data;
console.log(data);
return data;
});
};
return myService;
});
app.controller('MainCtrl', function( myService,$scope) {
$scope.asyncData = myService.async();
$scope.$watch('asyncData', function(asyncData) {
if(angular.isDefined(asyncData)) {
// Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
}
});
});
Небольшая демонстрация в coffeescript: http://plunker.no.de/edit/ksnErx?live=preview
Ваш плункер обновлен моим методом: http://plnkr.co/edit/mwSZGK?p=preview
Ответ 5
Лучше, я думаю, будет что-то вроде этого:
Услуги:
app.service('FruitsManager',function($q){
function getAllFruits(){
var deferred = $q.defer();
...
// somewhere here use: deferred.resolve(awesomeFruits);
...
return deferred.promise;
}
return{
getAllFruits:getAllFruits
}
});
И в контроллере вы можете просто использовать:
$scope.fruits = FruitsManager.getAllFruits();
Angular автоматически помещает разрешенный awesomeFruits
в $scope.fruits
.
Ответ 6
У меня была такая же проблема, но когда я занимался серфингом в Интернете, я понял, что $http возвратит по умолчанию обещание, после чего я смогу использовать его с "then" после возврата "данных". посмотрите код:
app.service('myService', function($http) {
this.getData = function(){
var myResponseData = $http.get('test.json').then(function (response) {
console.log(response);.
return response.data;
});
return myResponseData;
}
});
app.controller('MainCtrl', function( myService, $scope) {
// Call the getData and set the response "data" in your scope.
myService.getData.then(function(myReponseData) {
$scope.data = myReponseData;
});
});
Ответ 7
При привязке пользовательского интерфейса к вашему массиву вы захотите убедиться, что вы сразу же обновите тот же массив, установив длину в 0 и нажав данные в массив.
Вместо этого (который устанавливает ссылку на другой массив на data
, о котором не будет знать ваш пользовательский интерфейс):
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data = d;
});
};
попробуйте следующее:
myService.async = function() {
$http.get('test.json')
.success(function (d) {
data.length = 0;
for(var i = 0; i < d.length; i++){
data.push(d[i]);
}
});
};
Вот скрипка, которая показывает разницу между установкой нового массива против опорожнения и добавлением к существующему. Я не мог заставить ваш plnkr работать, но, надеюсь, это сработает для вас!
Ответ 8
В связи с этим я столкнулся с подобной проблемой, но не с получением или записью, сделанной Angular, а с расширением, сделанным третьим лицом (в моем случае Chrome Extension).
Проблема, с которой я столкнулся, заключается в том, что расширение Chrome не вернет then()
, поэтому я не смог сделать это в решении выше, но результат по-прежнему остается асинхронным.
Поэтому мое решение - создать службу и перейти к обратному сообщению
app.service('cookieInfoService', function() {
this.getInfo = function(callback) {
var model = {};
chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
model.response= response;
callback(model);
});
};
});
Затем в моем контроллере
app.controller("MyCtrl", function ($scope, cookieInfoService) {
cookieInfoService.getInfo(function (info) {
console.log(info);
});
});
Надеюсь, что это поможет другим получить ту же проблему.
Ответ 9
Я читал http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
[AngularJS позволяет нам оптимизировать нашу логику контроллера, поместив обещание непосредственно в область действия, а не вручную передавая разрешенное значение при успешном обратном вызове.]
так просто и удобно:)
var app = angular.module('myApp', []);
app.factory('Data', function($http,$q) {
return {
getData : function(){
var deferred = $q.defer();
var promise = $http.get('./largeLoad').success(function (response) {
deferred.resolve(response);
});
// Return the promise to the controller
return deferred.promise;
}
}
});
app.controller('FetchCtrl',function($scope,Data){
$scope.items = Data.getData();
});
Надеемся на эту помощь
Ответ 10
Мне действительно не нравится тот факт, что из-за "обещания" способа делать вещи потребитель службы, использующей $http, должен "знать" о том, как распаковать ответ.
Я просто хочу что-то назвать и получить данные, похожие на старый $scope.items = Data.getData();
способ, который теперь устарел.
Я пробовал какое-то время и не придумал идеальное решение, но вот мой лучший снимок (Plunker). Это может быть полезно кому-то.
app.factory('myService', function($http) {
var _data; // cache data rather than promise
var myService = {};
myService.getData = function(obj) {
if(!_data) {
$http.get('test.json').then(function(result){
_data = result.data;
console.log(_data); // prove that it executes once
angular.extend(obj, _data);
});
} else {
angular.extend(obj, _data);
}
};
return myService;
});
Затем контроллер:
app.controller('MainCtrl', function( myService,$scope) {
$scope.clearData = function() {
$scope.data = Object.create(null);
};
$scope.getData = function() {
$scope.clearData(); // also important: need to prepare input to getData as an object
myService.getData($scope.data); // **important bit** pass in object you want to augment
};
});
Недостатки, которые я уже могу заметить,
- Вам нужно передать объект, который вы хотите добавить в, который не является интуитивно понятным или распространенным шаблоном в Angular
-
getData
может принимать только параметр obj
в форме объекта (хотя он также может принимать массив), что не будет проблемой для многих приложений, но это больное ограничение
- Вы должны подготовить входной объект
$scope.data
с помощью = {}
, чтобы сделать его объектом (по существу, что $scope.clearData()
делает выше) или = []
для массива, или он не будет работать (мы уже имея в виду что-то о том, какие данные идут). Я попытался сделать этот шаг подготовки IN getData
, но не повезло.
Тем не менее, он предоставляет шаблон, который удаляет шаблонный планшет "обещающий разворот" и может быть полезен в тех случаях, когда вы хотите использовать определенные данные, полученные из $http в нескольких местах, сохраняя при этом DRY.
Ответ 11
Что касается кэширования ответа в службе, то здесь другая версия, которая кажется более прямой, чем то, что я видел до сих пор:
App.factory('dataStorage', function($http) {
var dataStorage;//storage for cache
return (function() {
// if dataStorage exists returned cached version
return dataStorage = dataStorage || $http({
url: 'your.json',
method: 'GET',
cache: true
}).then(function (response) {
console.log('if storage don\'t exist : ' + response);
return response;
});
})();
});
эта служба вернет либо кешированные данные, либо $http.get
;
dataStorage.then(function(data) {
$scope.data = data;
},function(e){
console.log('err: ' + e);
});
Ответ 12
Пожалуйста, попробуйте приведенный ниже код
Вы можете разделить контроллер (PageCtrl) и службу (dataService)
'use strict';
(function () {
angular.module('myApp')
.controller('pageContl', ['$scope', 'dataService', PageContl])
.service('dataService', ['$q', '$http', DataService]);
function DataService($q, $http){
this.$q = $q;
this.$http = $http;
//... blob blob
}
DataService.prototype = {
getSearchData: function () {
var deferred = this.$q.defer(); //initiating promise
this.$http({
method: 'POST',//GET
url: 'test.json',
headers: { 'Content-Type': 'application/json' }
}).then(function(result) {
deferred.resolve(result.data);
},function (error) {
deferred.reject(error);
});
return deferred.promise;
},
getABCDATA: function () {
}
};
function PageContl($scope, dataService) {
this.$scope = $scope;
this.dataService = dataService; //injecting service Dependency in ctrl
this.pageData = {}; //or [];
}
PageContl.prototype = {
searchData: function () {
var self = this; //we can't access 'this' of parent fn from callback or inner function, that why assigning in temp variable
this.dataService.getSearchData().then(function (data) {
self.searchData = data;
});
}
}
}());