$http.get doesn't work with REST model using then() function - angularjs

As you guys know, Angular recently deprecated the http.get.success,error functions. So this kind of calls are not recommended in your controller anymore:
$http.get("/myurl").success(function(data){
myctrl.myobj = data;
}));
Rather, this kind of calls are to be used:
$http.get("/myurl").then(
function(data) {
myctrl.myobj = data;
},
function(error) {
...
}
Problem is, simple Spring REST models aren't working with this new code. I recently downloaded a sample code with the above old success function and a REST model like this:
#RequestMapping("/resource")
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
This should return a map like {id:<someid>, content:"Hello World"} for the $http.get() call, but it receives nothing - the view is blank.
How can I resolve this issue?

The first (of four) argument passed to success() is the data (i.e. body) of the response.
But the first (and unique) argument passed to then() is not the data. It's the full HTTP response, containing the data, the headers, the status, the config.
So what you actually need is
$http.get("/myurl").then(
function(response) {
myctrl.myobj = response.data;
},
function(error) {
...
});

The expectation of the result is different. Its the response and not the data object directly.
documentation says :
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Properties of the response are
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
As the data object is required,
Please convert the code as
$http.get("/resource").then(
function(response) {
myctrl.myobj = response.data;
});

then must be return a new promise so you should handle it with defers.
var myApp = angular.module('myApp', []);
myApp.factory('modelFromFactory', function($q) {
return {
getModel: function(data) {
var deferred = $q.defer();
var items = [];
items.push({"id":"f77e3886-976b-4f38-b84d-ae4d322759d4","content":"Hello World"});
deferred.resolve(items);
return deferred.promise;
}
};
});
function MyCtrl($scope, modelFromFactory) {
modelFromFactory.getModel()
.then(function(data){
$scope.model = data;
})
}
Here is working fiddle -> https://jsfiddle.net/o16kg9p4/7/

Related

Undefined $http data when resolved with `.then` method

I used the console log which returned the array of data fetched from the server, but when I call the scope on the html I get an undefined error on the console.
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
this.getMoreData = function () {
blogpostservice.getMoreData($scope.pagec).then(function (data) {
$scope.posts = data;
console.log($scope.posts);
})
}
this.getMoreData();
}]);
HTML
<h1>{{pagec}}</h1>
<h1>{{posts[1].Title}}</h1>
<div id="posts" class="grid" ng-repeat="post in posts">
<div class=" grid-item">
<div class="blog-post">
<img src="https://placeimg.com/400/400/bbc" alt="">
<h3>{{post.Title}}</h3>
<img ng-src="{{post.Image}}" alt="">
</div>
</div>
</div>
The .then method of an $http promise returns a response object, of which data is one of several properties:
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
this.getMoreData = function () {
blogpostservice.getMoreData($scope.pagec)
̶.̶t̶h̶e̶n̶(̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶(̶d̶a̶t̶a̶)̶ ̶{̶
.then(function (response) {
͟v͟a͟r͟ ͟d͟a͟t͟a͟ ͟=͟ ͟r͟e͟s͟p͟o͟n͟s͟e͟.͟d͟a͟t͟a͟;͟
$scope.posts = data;
console.log($scope.posts);
})
.catch(function(response) {
console.log("Error: ", response.status);
throw response;
});
};
this.getMoreData();
}
]);
Also be sure to add a .catch handler to log rejected http requests.
For more information, see AngularJS $http Service API Reference.
UPDATE
i read the doc but connecting to the subject the main problem i made is calling it a data instead of a response on the function right?
The main problem is that the http promise does not resolve data. It resolves a response object. Data is only one of the properties of the response object:
$http(...)
.then(function onSuccess(response) {
// Handle success
var data = response.data;
var status = response.status;
var statusText = response.statusText;
var headers = response.headers;
var config = response.config;
...
})
.catch(function onError(response) {
// Handle error
var data = response.data;
var status = response.status;
var statusText = response.statusText;
var headers = response.headers;
var config = response.config;
...
});
From the Docs:
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
xhrStatus – {string} – Status of the XMLHttpRequest (complete, error, timeout or abort).
A response status code between 200 and 299 is considered a success status and will result in the success callback being called. Any response status code outside of that range is considered an error status and will result in the error callback being called. Also, status codes less than -1 are normalized to zero. -1 usually means the request was aborted, e.g. using a config.timeout. Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning that the outcome (success or error) will be determined by the final response status code.
— AngularJS $http Service API Reference.
Also note that a status of -1 usually is a symptom of a CORS problem. The request being blocked because of a violation of Same Origin Policy.
Rewrite the getMoreData function by using a callback.
See the code sample below
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount,callback) {
var result = $http.get('/api/posts/' + pagecount);
if(callback){
callback(result);
}
}
}]);
In essence since you do not know exactly when the getMoreData function will return a value from the http get method you pass the returned data of the http get into a callback function. You then utilize the data from the http get by implementing the callback method in your maincontroller like below:
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
this.getMoreData = function () {
blogpostservice.getMoreData($scope.pagec,function(data){ $scope.posts = data; console.log($scope.posts); })
}
this.getMoreData();
}]);
You also need to ensure you that an array is returned from $scope.posts.
Note that in your html you are interpolating {{post}} instead of {{posts}}

ExpressJS IP and AngularJS $http get

I'm trying to learn ExpressJS and I'm having trouble getting IP address from an Express route to display in the browser via Angular controller.
I'm using 2 Nodejs modules (request-ip and geoip2) to get the IP and then lookup geolocation data for that IP. Then trying to use Angular to display the geolocation data in the browser using an Angular $http get call.
My Express route for the IP:
// get IP address
router.get('/ip', function (req, res, next) {
console.log('requestIP is ' + ip);
// geolocation
geoip2.lookupSimple(ip, function(error, result) {
if (error) {
//return res.status(400).json({error: 'Something happened'});//default
return res.sendStatus(400).json({error: 'Something happened'});
}
else if (result) {
return res.send(result);
}
});
});
And my AngularJS controller code:
function MainController($http) {
var vm = this;
vm.message = 'Hello World';
vm.location = '';
vm.getLocation = function() {
$http({
method: 'GET',
url: 'localhost:8000/ip'
}).then(function (result) {
console.log(result);
return vm.location = result;
});
};
};
The Hello World message displays but not the location...? I can also go to localhost:8000/ip and see the JSON result. The result doesn't appear in Chrome's console either. The result is a json object like this:
{"country":"US","continent":"NA","postal":"98296","city":"Snohomish","location":{"accuracy_radius":20,"latitude":47.8519,"longitude":-122.0921,"metro_code":819,"time_zone":"America/Los_Angeles"},"subdivision":"WA"}
I'm not sure why the Hello Word displays and the location doesn't when it seems that I have everything configured correctly... so obviously I'm doing something wrong that I don't see...?
You have initialised 'vm.location' as a string when in fact it is a JSON object.
vm.location = {};
You need to adjust the url paramater in your request to:
url: '/ip'
As you are sending back JSON from Express.js, you should change your response line to:
return res.json(result);
Do you call vm.getLocation() somewhere in your code after this?
The data you need is under result.data from the response object.
Also in order to display the data in the html you have to specify which property to display from the vm.location object (vm.location.country, vm.location.city etc..).
From angular docs about $http:
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
Is this express js and angular hosted on the same port? If so please replace your
$http({
method: 'GET',
url: 'localhost:8000/ip'
}).then(function (result) {
console.log(result);
return vm.location = result;
});
with
$http({
method: 'GET',
url: '/ip'
}).then(function (result) {
console.log(result);
return vm.location = result;
});
It may be considered as CORS call and you have it probably disabled.
You can also specify second function to then (look code below) and see if error callback is called.
$http({
method: 'GET',
url: '/ip'
}).then(function (result) {
console.log(result);
return vm.location = result;
}, function (error) {
console.log(error);
});

Passing scope data into $http function

I'm constructing elements from metadata and i need to set a calculated class for each element.
This is what I currently do,
var promisses =_.map(templates, function (tmpl) {
return $http.get(tmpl.template, {
cache : $templateCache,
// Generated class name is carried to the resolving function using the config
classes : scope.generate_class(tmpl.columns)
}).then(function (data) {
if ( data.status != 200 )
throw new Error('Failed to fetch template');
var elm = angular.element(data.data);
elm.addClass(data.config.classes);
return elm;
});
});
$q.all(promisses).success....
If I want to use success instead of then fir the $http bit (which evaluates in case of an error as well) how would i do that ? when using success the config is not carried on to the resolving function (only the data).
Thanks.
From $http docs:
Returns a promise object with the standard then method and two http
specific methods: success and error. The then method takes two
arguments a success and an error callback which will be called with a
response object. The success and error methods take a single argument
- a function that will be called when the request succeeds or fails respectively. The arguments passed into these functions are
destructured representation of the response object passed into the
then method.
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
So you can pass the config like so:
.success(function(data, status, headers, config) {
Do not throw errors when using promises, if your server doesn't return an error code you can use q.reject to transform it to a rejection, also q.all promises doesn't have a success method:
var promisses =_.map(templates, function (tmpl) {
return $http.get(tmpl.template, {
cache : $templateCache,
// Generated class name is carried to the resolving function using the config
classes : scope.generate_class(tmpl.columns)
}).then(function(res) {
if ( res.status != 200 ) {
return $q.reject('Failed to fetch template');
} else {
var elm = angular.element(res.data);
elm.addClass(res.config.classes);
return elm;
}
});
});
$q.all(promisses)
.then(function() { ... })
.catch(function() { .. })

Get response header in then() function of a ngResource object's $promise property after resource resolved?

I'm willing to retrieve the response header of a resource request, cause I've put pagination information and something else in it rather than the response body, to make the REST api clear.
Though we can get it from the success / error callback like below:
Object.get({type:'foo'}, function(value, responseHeaders){
var headers = responseHeaders();
});
Where 'Object' is my resource factory service.
Further, when I'm trying to make the route change after required resources resolved, I've tried this:
.when('/list', {
templateUrl: 'partials/list.html',
controller: 'ListCtrl',
// wait for the required promises to be resolved before controller is instantialized
resolve: {
objects: ['Object', '$route', function(Object, $route){
return Object.query($route.current.params).$promise;
}]
}
})
and in controller, just inject "objects" instead of Object service, because it's resolved and filled in with real data.
But I got problem when I try to get headers info from the "objects" in controller.
I tried objects.$promise.then(function(data, responseHeaders){}), but responseHeader was undefined.
How can I change the $resource service's behavior so that it throws the responseHeader getter into the $promise then() callback function?
My service "Object" for reference:
myServices.factory('Object', ['$resource',
function($resource){
return $resource('object/:id', {id: '#id'}, {
update: {method: 'PUT'},
});
}
]);
I had the exact same problem. I used an interceptor in the resource definition to inject the http headers in the resource.
$resource('/api/resource/:id', {
id: '#id'
}, {
index: {
method: 'GET',
isArray: true,
interceptor: {
response: function(response) {
response.resource.$httpHeaders = response.headers;
return response.resource;
}
}
}});
Then, in the then callback, the http headers are accesible through $httpHeaders:
promise.then(function(resource) {
resource.$httpHeaders('header-name');
});
I think I had a similar problem: After POSTing a new resource I needed to get the Location header of the response, since the Id of the new resource was set on the server and then returned via this header.
I solved this problem by introducing my own promise like this:
app.factory('Rating', ['$resource',
function ($resource) {
// Use the $resource service to declare a restful client -- restangular might be a better alternative
var Rating = $resource('http://localhost:8080/courserater/rest/ratings-cors/:id', {id: '#id'}, {
'update': { method: 'PUT'}
});
return Rating;
}]);
function RestController($scope, $q, Rating) {
var rating = new Rating();
var defer = $q.defer(); // introduce a promise that will be resolved in the success callback
rating.$save(function(data, headers){ // perform a POST
// The response of the POST contains the url of the newly created resource
var newId = headers('Location').split('/').pop();
defer.resolve(newId)
});
return defer.promise;
})
.then (function(newId) {
// Load the newly created resource
return Rating.get({id: newId}).$promise; // perform GET
})
.then(function(rating){
// update the newly created resource
rating.score = 55;
return rating.$update(); // perform PUT
});
}
We can't use .then for returning the header because the promise doesn't allow for multiple return values. (e.g., (res, err))
This was a requested feature, and was closed https://github.com/angular/angular.js/issues/11056
... the then "callbacks" can have only [one] argument. The reason for this is that those "callbacks" correspond to the return value / exception from synchronous programming and you can't return multiple results / throw multiple exceptions from a regular function.

Cancelling a request with a $http interceptor?

I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}

Resources