AngularJS $http POST request different results with then and success - angularjs

Something is driving me nuts; maybe someone can help me out with the following? :
I am using AngularJS 1.2.26 (have to because IE8 needs to be supported)
I have to invoke some backend services that were initially build for a backbone frontend. I managed to do that in the following way:
$http({
method: 'POST',
url: url,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: this._transformRequest,
data: formData
})
.success(function (data) {
// bla bla not relevant
}).error(function (error) {
// bla bla not relevant
});
Now i try to work with the then function as i find that more consequent, so i change the code into:
$http({
method: 'POST',
url: url,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: this._transformRequest,
data: formData
}).then(
function (response) {
// not relevant
}, function (error) {
// not relevant
});
According to me, in theory this should have the same result as the initial implementation, however to my surprise the request now fails on the server. While debugging I noticed that the result of the transform request function delivers a very different result in both scenario's in the request that is handled with success and error the result of transform request is as follows:
com.bank.token.session=XXXXX&model=%7B%22relatieRol%22%3A%22AANVRAGER%22%2C%22evaUitgevoerdDat%22%3Anull%2C%22sfhUitgevoerdDat%22%3Anull%2C%22bkrUitgevoerdDat%22%3Anull%2C%22bkrBekendCd%22%3A%22GOED_BEKEND%22%7D
When i use the 'then' function as the way to handle the result the transformRequest function returns the following (wrong) result:
com.bank.token.session=XXXXXXXXXXX&model=%7B%22data%22%3A%7B%22relatieRol%22%3A%22AANVRAGER%22%2C%22evaUitgevoerdDat%22%3Anull%2C%22sfhUitgevoerdDat%22%3Anull%2C%22bkrUitgevoerdDat%22%3Anull%7D%2C%22status%22%3A200%2C%22config%22%3A%7B%22method%22%3A%22POST%22%2C%22transformResponse%22%3A%5Bnull%5D%2C%22url%22%3A%22http%3A%2F%2Flocalhost%2Femployee%2Findex.html%2Ffoo-web%2Fxchannel-foo-secure-portlet%2F1598792178%2Fver%3D2.0%2Fresource%2Fid%3Dfoo-fetch-%2Frparam%3Dportal%3DfooPortal.wsp%22%2C%22headers%22%3A%7B%22Content-Type%22%3A%22application%2Fx-www-form-urlencoded%22%2C%22Accept%22%3A%22application%2Fjson%2C%20text%2Fplain%2C%20*%2F*%22%7D%2C%22data%22%3A%7B%22com.bank.token.session%22%3A%22XXXXXXXXXX%22%2C%22model%22%3A%22%7B%5C%22relatieRol%5C%22%3A%5C%22AANVRAGER%5C%22%7D%22%7D%7D%2C%22statusText%22%3A%22OK%22%2C%22bkrBekendCd%22%3A%22GOED_BEKEND%22%7D
This really surprises me; how can the handler on the $http service influence the way the request is handled? I would like to use 'then' for handling my $http POST; but it seems not to work. Anybody knows why? Many thanks in advance!
my transformRequest function looks like this:
_transformRequest: function (obj) {
var str = [];
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
}
console.log('transform: ', str.join("&"));
return str.join("&");
}

success and error unpack the data property of the response for you (as well as route to the pretty names). So, if you change to then you need to manually address the data property of the response in order to get the same information:
$http({
method: 'POST',
url: url,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: this._transformRequest,
data: formData
}).then(
function (response) {
var data = response.data;
// not relevant
}, function (error) {
var data = error.data;
// not relevant
});
Here is the relevant part in the $http documentation:
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.

The $http legacy promise methods success and error have been deprecated. Use the standard then method instead.1

Related

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

How to unit-test the responseType in AngularJS

I have a method that dynamically sets responseType attribute of the $http request based on the asset type that's being requested. I'd like to unit-test that the correct response type is being set using Jasmine.
From what I found, you can expect a request with certain headers, but responseType is not a header, it's a part of the request config. Here are the samples of my code (in TypeScript).
let config = {
headers: {
'Content-Type': contentType
}
}
if (contentType.startsWith('image')) {
config.responseType = 'arraybuffer';
}
$http.get(url, config);
Ok, this is a bit late, and I've spent quite a while on this, but I've finally gotten this to work.
Assuming you're reconstructing the $httpBackend in a beforeEach hook (mine's assigning it to a variable called 'backend'... in the App config (or perhaps a more global beforeEach hook, haven't tried it that way), you'll need to add a decorator function to the $httpBackend service:
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
$delegate.interceptedCalls = [];
var $httpBackend = function (method, url, data, callback, headers, timeout, withCredentials, responseType,
eventHandlers, uploadEventHandlers) {
$delegate.interceptedCalls.push({
method: method,
url: url,
timestamp: new Date(),
type : responseType
});
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
All that does is add an interceptedCalls property to your backend object which will contain a list of all the requests that go through it.
Then, in your test file, you can do something like this:
it("forwards the data correctly",function(){
var headers = {
'Content-Type' : 'application/json',
'Accept': 'application/json, text/plain, */*'
};
backend.expectPOST('/someRoute/',{"A" : "B"},headers)
.respond(200,"dsfkjlasdfasdfklsdfd");
service.functionThatCallsSomeRoute({"A" : "B"});
backend.flush();
expect(backend.interceptedCalls[0]['type']).to.equal('arraybuffer');
});
May not be the best way to do it, but since I'm essentially refreshing the whole thing (backend and service being tested) before every test, it will have all the calls in order on the object.

Angular JS 1.X Access Resolved Promise Objects values

I have read a lot of posts on promises,resolving promises, and accessing the data however I cannot seem to. Following some posts on Stack Overflow has just caused errors, so I am not sure what exactly I am doing wrong.
I have a function like so:
function getJsonl() {
var deferred = $q.defer();
$http({
url: 'urlNotShownForSecurity',
dataType:"json",
method: 'GET',
data:{"requestId":"123"},
headers:{"Content-Type":"application/json","requestId":"123"},
}).success(function(data) {
deferred.resolve(data);
console.log(data)
}).error(function(error,status,headers,config) {
deferred.reject(error);
});
return Promise.resolve(deferred.promise);
}
Here I return a json promise that has been resolved resulting in a json object I believe.
Printing to console I get the following:
Inside data is the information I need, it looks like this:
data:Array[8]
0:Object
description:"My description paragraph"
I have tried things with the returned object in my controller like:
vm.result = data.data[0].description;
vm.result = data[0].description
I have tried many different approaches in the view as well to access but I get 2 blank li tags and that is it.
I would like to be able to access the data so I populate a table. So if I can use it with ng repeat that would be great, as well as being able to access without because some data is used in more than just the table.
Update
#DanKing, following your implementation I get the following output in console:
Now I am back with a promise object.
It looks to me as though you're fundamentally misunderstanding the nature of promises.
$http() is an asynchronous function - that means it doesn't complete straight away, which is why it returns a promise.
It looks to me as though you're trying to call $http() and then get the result back and return it from your getJson1() method, before $http() has finished executing.
You can't do that. Your getJson1() method should just return the promise, so your calling method can chain onto it - like this:
getJson1().then(function(data) {
// do some other stuff with the data
});
The whole point of promise chains is that they don't execute straightaway - instead you provide callback functions that will be executed at some indeterminate point in the future, when the previous operation completes.
Your getJson1() function just needs to do this:
return $http({
url: 'urlNotShownForSecurity',
dataType:"json",
method: 'GET',
data:{"requestId":"123"},
headers:{"Content-Type":"application/json","requestId":"123"},
});
getJsonl().then(function(data){
console.log(data);
},function(err){
console.log(err);
})
should work. Where is your $http request and where is your call to getJsonl() will also make a difference. So choose that carefully when implementation. If you are using this in a service then you will have to return the function result say
this.somefunction = function (){
return getJonl();
}
and in your controller inject the service and do the following
service.somefunction().then(function(data){
console.log(data);
},function(err){
console.log(err);
})
Ok, rewrote the answer as a complete component to show the moving parts.
$http returns a promise so your original getJsonl call can be simplified. Using your original $http parameters this should dump your API response to the screen if you use the <json-thing> tag:
angular.module('yourModuleName')
.component('jsonThing', {
template: '<pre>{{$ctrl.thing|json}}</pre>',
controller: function ($http) {
var $ctrl = this;
getJsonl()
.then(function (response) {
console.log(response); // we have the $http result here
$ctrl.thing = response.data; // allow the template access
}, function (error) {
console.log(error); // api call failed
});
// call backend server and return a promise of incoming data
function getJsonl() {
return $http({
url: 'urlNotShownForSecurity',
dataType: 'json',
method: 'GET',
data: { requestId: '123'},
headers: { 'Content-Type': 'application/json', requestId: '123'}
});
}
}
});

What is type of data angular sending?

What is type of data angular sending? I use laravel + angular. I`m trying, but this script return 405 error. Method not allowed.
.controller('adminCtrl', function( $scope, $http ){
$scope.collection = [];
$scope.newData = [];
$scope.newrecord = function() {
$scope.collection.push($scope.newData);
$http({
url: '/newrecord',
method: "POST",
data: $.param($scope.collection),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(data){
console.log(data);
})
}
})
You are getting 405 - Method not Allowed because the server you are sending your request does not have POST it the white list of methods allowed to be used to perform requests to that given API.
It's not an angularJS issue, it's a server configuration issue.
$http sends data as json.
You do not need to serialize params using "$.param", data is plain javascript object, which is send to your REST endpoint.
So attach just "$scope.collection) and do not set Content Type manually, it is json by default.
POST can be send also with convenience method.
$http.post('/someUrl', data, config).then(successCallback, errorCallback);

Set defaults header on AngularJS but don't use it on one specific request

For sending OAuth2 token I am setting up defaults header on AngularJS like this:
$http.defaults.headers.common['Authorization'] = 'Bearer ' + access_token;
This works great but I don't need this header (I get an error) for one specific request.
Is there a way of excluding defaults header when performing that request?
Thanks!
SOLVED
Thanks to Riron for getting me on a right path. Here's the answer:
$http({
method: 'GET',
url: 'http://.../',
transformRequest: function(data, headersGetter) {
var headers = headersGetter();
delete headers['Authorization'];
return headers;
}
});
When you make your call with $http, you can override defaults headers by providing them directly in your request config:
$http({method: 'GET', url: '/someUrl', headers: {'Authorization' : 'NewValue'} }).success();
Otherwise you could transform your request using the transformRequest parameter, still in your $http config. See doc :
transformRequest – {function(data,headersGetter)|Array.<function(data, headersGetter)>} – transform
function or an array of such functions. The transform function takes
the http request body and headers and returns its transformed
(typically serialized) version.
This way you could delete an header for a single request before it's being send:
$http({method: 'GET',
url: '/someUrl',
transformRequest: function(data,headersGetter){ //Headers change here }
}).success();
For latecomers, whilst the solution might have worked - you actually shouldn't need to use transformRequest for this.
The Angular docs for the $http service actually have this exact situation covered:
To explicitly remove a header automatically added via
$httpProvider.defaults.headers on a per request basis, Use the headers
property, setting the desired header to undefined.
For example:
var req = {
method: 'POST',
url: 'http://example.com',
headers: {
'Content-Type': undefined
},
data: {
test: 'test'
}
}
$http(req).success(function(){...}).error(function(){...});
Angular 1.4.0 can no longer modify request headers using transformRequest:
If one needs to dynamically add / remove headers it should be done in
a header function, for example:
$http.get(url, {
headers: {
'X-MY_HEADER': function(config) {
return 'abcd'; //you've got access to a request config object to specify header value dynamically
}
}
})
While the $httpProvider can override $http the use of intereceptors are 1 way of handling this, I end up doing it this way
function getMyStuff(blah) {
var req = {
method: 'GET',
url: 'http://...',
headers: {
'Authorization': undefined
}
}
return $http(req)
.then(function(response) {
return response.data;
});
}

Resources