a really simple example. I have a RESTful api and I setup my resource the following way.
app.factory('apiFactory' , ['$resource', 'GLOBALS',
function($resource, GLOBALS){
return {
Discounts: $resource(GLOBALS.apiPath + 'discounts/:id', {id:'#id'}, {update:{method: 'PUT'}})
}
}
])
And then I call it in a Controller like so
var discountResponse = apiFactory.Discounts.save($scope.discount);
Everything works fine until I add '/:id' to my URL. I do this so that my delete method passes the id along. Like so 'discounts/6'.
The issue that I have is that as soon as I add the placeholder my save() method sends off a GET instead of a POST.
Request URL:http://local:8089/api/discounts
Request Method:GET
Status Code:200 OK
If I remove the placeholder I get
Request URL:http://local:8089/api/discounts
Request Method:POST
Status Code:200 OK
And everything works great, accept for the delete request, which now does not map the placeholder, as it no longer exists.
I have absolutely no idea why. I'm pretty new to $resource, so I am very sure I am not understanding something.
The answer was provided on a differently formulated question and I thought I'd share it.
return {
Discounts: $resource(GLOBALS.apiPath + 'discounts/:id', {id:'#id'} ,{
save: {
method: 'POST', url: GLOBALS.apiPath + "discounts"
},
update: {
method: 'PUT', url: GLOBALS.apiPath + "discounts/:id"
}
})
}
It would seem that for the save() to POST properly I had to define a path in the customConfig object. I'm not sure why this didn't work for me out of the box.
The answer was provided here. Many Thanks!
ngResource save() strange behaviour
Related
My REST backend [ based on NodeJS/express/mongojs] is complaining 404 (not found) when Params are attached as part of URL. Backend rest interface is coded as below;
var express = require('express');
var router = express.Router();
router.get('/login', auth.signin); //auth.signin is code to verify user
Above REST service is consumed by AngularJS based frontend through $resource as below;
Definition:
angular.module('myapp').factory('signinmgr', function($resource) {
return $resource("http://localhost:3000/login", {}, {
'get': {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}});
Usage:
signinmgr.get({'username':'myname', 'password':'mypass'}, function(data){
//success
}, function(x){
//failed
});
Problem:
Frontend code above produces a URL to consume REST service where parameters are part of URL i.e. http://localhost:port/login?username=myname&password=mypass [if I use GET method, POST is OK]. I wanted my front end to keep URL as http://localhost:port/login and post any parameters through body as backend is using req.body.paramName to read those. [Actual Solution]
If (1) cannot be done, and my frontend is sending params as part of URL, I needed help as to know how to equip my backend to allow this URL with parameters so that backend doesnt return 404 as the base URL http://localhost:port/login is already there.
PS: for (1), I tried this thread with data:{username:'',password:''} but of no use. Please help if I am missing something very obvious or some concept.
Try the $http service instead:
angular.module('myapp').factor('signinmgr', function($http) {
return {
login: function (username, password) {
$http.post("http://localhost:3000/login", {
username: username,
password: password
}
}
};
});
signinmgr.login('myname', 'mypass').then(function(data){
//success
}, function(x){
//failed
});
Each request that my nodejs/expressjs backend receives has three places for passed attributes;
params{}
query{}
body{}
My problem (1) cannot be fixed in case I want to use GET method since with GET request parameters are visible as part of URL i.e. http://localhost:port/login?username=myname&password=mypass. To send my username/password I had to use POST that sends parameters as part of body{}.
My problem (2) was that I was using GET and mistakenly looking for parameters in body{} of request. Instead, parameters passed as part of URL in GET request are added to query{} of the request.
WORK AROUND IS AT THE BOTTOM
Original problem
There are question like this all over the web and none of them really have answer for me. I can't get an http PATCH operation to work using angular to save my life. I've implemented $http, with shortcut $http.patch and without using the config object method:PATCH. I've used $resource by adding a custom method. And I've implemented Restangular using their patch and I'm getting the same error. I have the correct Content-Type as suggested in other posts. I think it's safe to say at this point, it's something I'm missing. I'm getting the same "404" message via postman when trying to patch. I can PUT, GET, POST, and DELETE, but not PATCH.
In the following images you can see that the resource exists for GET. But when trying to patch I get 404. Browsing to that endpoint shows the record. Which is stored in Mongodb.
Here's some code snippets:
Resangular GET:
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.get().then(function(res) {
console.log(res)
}, function(err) {
console.log('Restangular failed: ', err)
});
Restangular Patch:
var data = {
corporiumId: $scope.newBlock
};
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.patch(data).then(function(res) {
console.log(res)
}, function(err) {
console.log('Restangular failed: ', err)
});
$http attempt using config object:
controller code:
httpCorporiumSrv.updateCorporiumId('/corporium-mgmnts/' + $scope.params.id, data)
.then(handleUpdateSuccess)
.catch(handleUpdateError);
service code, tried forcing the content-type header but got same result
with or without it:
function updateCorporiumId(url, data) {
return $http({
method: 'PATCH',
url: url,
data: angular.toJson(data),
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
//transformRequest: transformUpdateData
})
.then(handleUpdateSuccess)
.catch(handleUpdateErrors);
}
Using the .patch shortcut:
function updateCorporiumId(url, data) {
return $http.patch(url, data, {
transformRequest: transformUpdateData
})
.then(handleUpdateSuccess)
.catch(handleUpdateErrors);
}
Thing is I've tried this every which way I know how. I don't even know how to start debugging any more. I'm just getting 404 on a resource that does exist. Any suggestions on what might be happening to my request would be great.
Resolution:
For anyone having this issue, if you could post the fix or what's going on here to this point or PM me that would be awesome I'd like to know. I ended up just using PUT to fix this.
Quick Restangular solution:
Build the url template for findByOne like function using Restangular.one(url, _id) where '_id', is the id of the resource you're looking for. .get() goes out and finds that one resource by said id, which you can populate dynamically however you like. Once you have the one resource with GET copy it with Restangular.copy() which is different from angular.copy as it doesn't bind 'this' to the new object. Change what needs to be changed or added in the new object and then perform a .put() on it.
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.get().then(function(res) {
var update = Restangular.copy(res);
// update date corporiumId
update.corporiumId = $scope.newBlock;
// submit new doc with altered value
update.put().then(function() {
console.log('updated')
});
console.log(update)
}, function(err) {
console.log('Restangular failed: ', err)
});
Also because mongo uses _id and Restangular uses id you have to add this to your module
angular.module('corporium-mgmnts').config(function(RestangularProvider) {
RestangularProvider.setMethodOverriders(['put', 'patch']);
// setRestangularFields is required for mongodb
RestangularProvider.setRestangularFields({
id: "_id"
});
});
i am working on a project using Play Framework 2.4 using a simple Java template, as in many documentation i have seen, i just wrote a simple controller with my business methods and put the necessary paths on my route's file.
Now i am writing a client in Angular.js to invoke the logic written in the play app. It had work perfectly with GET methods, but when i try to do a POST from angular using the next lines:
$http({
method: 'POST',
url : rootURL + '/user/company',
data : {id : '123456' , name: 'xxxxxx'}
});
I receive a 404 Error. After several hours of forums searching, i found that the Play App is expecting application/x-www-form-urlencoded in the request content-type header.
So i modify my Angular call to the following:
$http({
method: 'POST',
url : rootURL + '/user/company',
data : {id : '123456' , name: 'xxxxx'},
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj){
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
}
})
};
And the POST works perfectly on this way, but i wonder, is there any way so i can make a POST request to my play app where the content-type is set to application/json? How can achieve this?
UPDATE
Here is my controller code:
public class MyController extends Controller {
public Result myAction(){
//do funny stuffs
}
}
And my route file has the following
POST /path/action controllers.MyController.myAction()
Have you added #BodyParser.Of(BodyParser.Json.class) in your action method?
Also when you are doing POST to a different domain, first check you request to see if it's a POST, I don't know about AngularJS, but with Polymer, it sent a OPTION first during ajax POST to different domain, and I met very similar issue (wrong content type and 404) which is resolved by implementing an OPTION on server which accept POST.
To Fix that, you need to manually implement an OPTION, here's what to do:
First, In your routes file, add an entry to accept OPTION
OPTIONS /*url controllers.Application.optionCheck(url)
Then implement the optionCheck method which accept everything
public Result optionCheck(String url){
response().setHeader("Access-Control-Allow-Origin", "*");
response().setHeader("Access-Control-Allow-Methods", "POST");
response().setHeader("Access-Control-Allow-Headers", "accept, origin, Content-type, x-json, x-prototype-version, x-requested-with");
return ok();
}
And that's it
I basically call get requests like so:
var resource = $resource('/api/v1/categories/:id')
resource.get({id: 1}).$promise.then(function(data){
console.log(data)
})
This works fine.. but how do I get the response headers?
You could use the transformResponse action defined here this would allow you add the headers
$resource('/', {}, {
get: {
method: 'GET',
transformResponse: function(data, headers){
response = {}
response.data = data;
response.headers = headers();
return response;
}
}
See a working example here JSFiddle
#Martin's answer works for same domain requests. So I would like to add to his answer that if you are using cross domain requests, you will have to add another header with Access-Control-Expose-Headers: X-Blah, X-Bla along with the Access-Control-Allow-Origin:* header.
where X-Blah and X-Bla are custom headers.
Also you do not need to use transform request to get the headers. You may use this code:
var resource = $resource('/api/v1/categories/:id')
resource.get({id: 1}, function(data, headersFun){
console.log(data, headersFun());
})
See this fiddle.
Hope this helps !!!
Old question, but i think it's worth mentioning for the future reference.
There is 'official' solution for this in angular documentation:
It's worth noting that the success callback for get, query and other
methods gets passed in the response that came from the server as well
as $http header getter function, so one could rewrite the above
example and get access to http headers as:
var User = $resource('/user/:userId', {userId:'#id'});
var users = User.query({}, function(users, responseHeaders){
// ...
console.log(responseHeaders('x-app-pagination-current-page'));
});
(code from docs slightly updated for clarity)
For CORS requests, exposing headers as mentioned in other answers is required.
$resource is not correctly passing along a url parameter when making a PUT request, via custom action.
This is my service creating the resource.
.factory('cartItemsService', ['$resource', function($resource) {
return $resource('/api/cart/:cartId/items/', {format: 'json'}, {
get: {method: 'GET', isArray: true},
update: {method: 'PUT', isArray: true},
});
}])
In my controller I'm trying to update the list of items like this. Note that $scope.cart.id exists and is correct (in this case 1)
$scope.cartItems = cartItemsService.update({cartId: $scope.cart.id});
However the request URL is: /api/cart/items/ but I'm expecting /api/cart/1/items/. This works fine if I do .get({cartId: <some_id>}) but doesn't seem to work for update.
EDIT: Angular version 1.1.5
In the end this was due to the request headers I was setting before making the request.
I was attempting to set put headers like such:
$http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken;
This is what caused the request url to be incorrectly formatted.
Changed it to set the post header instead, and it worked.
$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;