AngularJS $resource service's $save() POSTs to incorrect route - angularjs

I have a basic factory returning a $resource called User:
angular.module('appServices').factory('User', [
'$resource',
function($resource){
return $resource('http://localhost:3000/users/:username', {username:'#username'});
}]);
In my UserController.js file I try to do something fairly simple:
angular.module("myApp").controller('UserController', ['$scope', '$routeParams', 'User',
function($scope, $routeParams, User){
var user = new User({email:'user#example.com', username: 'johndoe', password: 'mySecurePassw0rd!'});
user.$save();
}]);
I expect the $save() function to make a POST request to /users, however in the console I get http://localhost:3000/users/johndoe 404 response, because the route is obviously not set up on the server for POST requests with the username parameter appended...
Why is it that it does so in my code sample ? From the examples I have seen on the internet, the $save() function does not take into account the username and should directly POST to /users in my case.
Any help would be appreciated!
EDIT
I think I got the error (as always, immediately after posting the question on SO...)
It is probably because of the default parameter {username: "#username"} I specified in the $resource, as it expects there to be a username parameter by default ?
So the correct way to do it would be to return this resource from the factory:
return $resource('http://localhost:3000/users/:username');
correct ?

Username is a natural primary key. Unless you use switch to a surrogate one (generated on server side), you will need two different methods for entity creating and updating:
var User = $resource(
'/users/:username',
{
username: '#username'
},
{
save: {
methods: 'POST',
url: '/users'
},
update: {
method: 'PUT'
}
}
);

Related

Unable to create POST request to REST API with $resource in angularjs

I am learning about the MEAN stack, and have created a REST API which posts a review to a collection in MongoDB.
I have defined a service as given:
angular.module('myApp')
.constant('baseURL', 'http://localhost:8080/');
angular.module('myApp')
.service('addReviews', ['$resource', 'baseURL', function($resource, baseURL) {
this.getReviews = function() {
return $resource(baseURL+'reviews/', null, {'save': {method: 'POST'}});
};
}]);
Now, I am calling this service from my controller:
angular.module('myApp', ['ngResource'])
.controller('reviewController', ['$scope', 'addReviews', function($scope, addReviews) {
$scope.reviewSubmit = function() {
$scope.receivedReviews = false;
var review = {
// some data
};
$scope.reviews = addReviews.getReviews().query(
function(response) {
$scope.reviews = response;
$scope.receivedReviews = true;
},
function(response) {
$scope.reviews = response;
// print error message
}
);
console.log($scope.reviews); // showing empty array
};
}]);
In routes.js, I have configured my route as:
var Reviews = require('./models/reviews');
...
app.post('/reviews', function(req, res) {
Reviews.create(req.body, function(err, post) {
if (err) {
return res.send(err);
}
return res.json(post);
});
});
I am trying to post a new review to the Reviews collection. However, $scope.reviews is showing an empty array. I logged the requests, and it shows a GET request is being to /reviews instead of POST. I think I should use save() instead of query(), but I have seen some tutorials online where they used query() despite the method being PUT/POST in the service. I am really confused. Can anyone point out how I can post the data (in var review) to the Reviews collection?
There are some issues with your code on the angular side of things.
You want to use $resource as an all-purpose object to communicate with the API. It has built-in functionality to:
query: get all resources from a given API endpoint
get: a single resource, usually by specifying that resource's id
save: post, with an object sent across in the body of the request. NOTE: you don't need the {'save': {method: 'POST'}} in your $resource configuration, you get it for free.
remove and delete: self-explanatory
So you'd want to set up your reviews factory (incl. url constant) like:
angular.module('myApp', ['ngResource'])
.constant('baseURL', 'http://localhost:8080/')
.factory('Reviews', ['$resource', 'baseURL', function($resource, baseURL) {
return $resource(baseURL+'reviews/:id', {id: '#id'});
}]);
If you want to have access to all saved reviews in your controller, as $scope.reviews, you'd do something like:
angular.module('myApp')
.controller('reviewController', ['$scope', 'Reviews', function($scope, Reviews) {
// hit API endpoint to get all reviews
// will have to have app.get('/reviews', function(req, res) {...})
// configured in your node code
Reviews.query(function(data) {
$scope.reviews = data;
}, function(error) {
console.log(error);
});
// and if you want to take a user-written review, say $scope.userReview,
// from the view and save it to the database on click function submitReview()...
$scope.userReview = {
message: '',
createdTime: null
};
// ^ not sure what your ReviewSchema looks like on the backend, but for example...
$scope.submitReview = function() {
if ($scope.userReview.message.length) {
$scope.userReview.createdTime = Date.now();
Reviews.save($scope.userReview);
// ^ this will make POST request with the $scope.userReview object as the request body
}
};
}]);
The create method on your back end looks fine. The object (or maybe just string) you send across will have to match your review schema. You may want to log the request body to make sure you're getting what you expect.
Have a look at this short post on using $resource to interact with RESTful APIs, and (the slightly more confusing) angular $resource docs, for more information on the $resource service.
Hope this helps you!

AngularJS POST with $resource sending in query string, what am I doing wrong?

I'm a bit of a noob with Angular and am having issues trying to post to a Drupal Services endpoint. I can post just fine with HttpRequester (FFox plugin), however all my attempts with Angular to post data to get a session result in 401 Unauthorized: missing required argument username or other errors.
Here is my testing factory resource with default input:
userInfoApp.factory('LoginService', function($resource) {
return $resource('/auth-service/user/login', {username: 'admin', password: 'admin'}, {
update: {
method: 'POST', // this method issues a POST request
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}
}
});
});
Here is the call I am making to it inside the controller (this):
this.login = function() {
var login = LoginService.update(function(data) {
console.log(data);
});
};
So far this usually results in a query string generated like so:
http://project.loc/auth-service/user/login?password=admin&username=admin
and the response of:
401 Unauthorized : Missing required argument username
What might I be doing wrong here? I have gotten $resource to work just fine with other endpoints (like for a menu service to retrieve a menu) however posting seems to be much more finicky. Any suggestions would be appreciated.
Thanks
Your current configuration for $resource sends username & password as querystring. Hence they appear in your URL. I assume you need these values to be POST.
According to documentations all non GET methods have a payload parameter in the action method:
non-GET "class" actions: Resource.action([parameters], postData,
[success], [error])
What you need to do is stop sending the parameters as default [parameters] and make use of the postData to POST data to your Drupal Services endpoint. You could do this when you call $update() as:
LoginService.update({}, {username: "admin",password: "admin"});
Ok, I found a way that works; for the controller function:
this.login = function() {
var data = {
username: this.username,
password: this.password
};
var login = LoginService.save({}, data,
function(data) {
// possibly do stuff with result
},
function(reply) {
// Comments on best way to access $scope here are welcome.
// This feels a bit weird to me.
$scope.info.errorMessage = reply.statusText;
}
);
};
And for the factory service:
// Login factory service
userInfoApp.factory('LoginService', function($resource) {
return $resource('/auth-service/user/login');
});
This actually retrieves the session id and other user data from the server.

Change the base URL for a resource

I'm using Angular to consume a RESTful API on the same application. I have a $resource setup for the contacts resource at http://sample-site.com/api/contacts
This is great and it works, however I need to interact with the basic CRUD of /api/contacts on different pages of the application.
For example, I need to interact with contacts again at another page on web app hosted at http://sample-site/friends/{friend-id}. However when I try to use my contact resource on that page, the url to the resource is appended to the current URL:
GET | http://sample-site/friends/1/api/contact
but what I really need is to GET | http://sample-site/api/contact on that resource on that page.
Is there a way to configure a resource to define a different base URL other than in relation to the current page?
This is my attempt to change the base URL inside the resource:
.factory('ContactRest', ['$resource', '$location', function($resource, $location) {
var host = $location.host()
return $resource('https://:url/:action', {}, {
// Normal CRUD actions
query: {
url : host,
action : 'api/contact',
method : 'GET',
isArray : true,
}
}]);
Which makes https://sample-site/friends/1/sample-site/api/contact. Regardless of how I define the $resource, just keeps appending any URL's to the base URL. I need to change the very base of the URL.
I got it!
.factory('ContactRest', ['$resource', '$location', function($resource, $location) {
var host = $location.host()
return $resource('http://' + host + ':action', {}, {
// Normal CRUD actions
query: {
method : 'GET',
isArray : true,
}
}]);
/**
* In controller
*/
ContactRest.query({ action: 'api/contact' }, function(contact) {
console.log('Got it, take that Havascript!');
});

AngularJS - Error: value.push is not a function

I have a service defined like this ->
storegrServices.factory('productList', ['$resource', function($resource) {
return $resource('', {},{
query: {url:'/apis/productshome.php', method:'GET', isArray:false},
getProductDetail: {url:'/apis/getproductdetail.php', method:'POST', isArray:true}
});
}]);
And in the controller this is how I have called the POST ->
storegrControllers.controller('productPageCtrl', ['$scope','productList','$routeParams', function($scope, productList, $routeParams) {
$scope.postVariable = new productList();
$scope.postVariable.productcode = $routeParams.code;
$scope.postVariable.$getProductDetail();
}]);
This sends the product code to the server and the server returns with an array in the response. I have used isArray: true, however it still keeps giving me an error. I don't a 'value' variable/object in my code.
Please suggest how can I fix this.
This is the complete error that I get ->
Error: value.push is not a function
resourceFactory/</Resource[name]/promise</<#http://storegr.com/js-lib/angular-resource.js:530
q#http://storegr.com/js-lib/angular.min.js:7
resourceFactory/</Resource[name]/promise<#http://storegr.com/js-lib/angular-resource.js:529
yd/e/k.promise.then/w#http://storegr.com/js-lib/angular.min.js:92
yd/e/k.promise.then/w#http://storegr.com/js-lib/angular.min.js:92
yd/g/<.then/<#http://storegr.com/js-lib/angular.min.js:93
zd/this.$get</h.prototype.$eval#http://storegr.com/js-lib/angular.min.js:101
zd/this.$get</h.prototype.$digest#http://storegr.com/js-lib/angular.min.js:98
zd/this.$get</h.prototype.$apply#http://storegr.com/js-lib/angular.min.js:101
f#http://storegr.com/js-lib/angular.min.js:66
F#http://storegr.com/js-lib/angular.min.js:70
md/</B.onreadystatechange#http://storegr.com/js-lib/angular.min.js:71
http://storegr.com/js-lib/angular.min.js
Line 84
This is the response from the server ->
[{"sno":21,"cat1":"Beverages","cat1code":"B","cat2":"Carbonated Drinks&Fruit Drinks","cat2code":"CDD","cat3":"Fruit Juices","cat3code":"FJ","cumulative":20,"brand":"Real Juice","product":"Apple","productcode":"B-CDD-FJ-1","imagename":"B-CDD-FJ-1","weight":200,"unit":0,"mrp":20,"margin":0,"wsp":0,"vat":0,"hbprice":0,"discount":0,"availableqty":0,"availableprice":0,"existingqty":0,"existingprice":0,"addedqty":0,"addedprice":0,"soldqty":0,"soldprice":0,"tags":"home"}]
I was able to work this around, answering it here so that it could benefit some one else too.
I modified my array response to be a json object like this and it worked.
{ "product":
[{"sno":21,"cat1":"Beverages","cat1code":"B","cat2":"Carbonated Drinks&Fruit Drinks","cat2code":"CDD","cat3":"Fruit Juices","cat3code":"FJ","cumulative":20,"brand":"Real Juice","product":"Apple","productcode":"B-CDD-FJ-1","imagename":"B-CDD-FJ-1","weight":200,"unit":0,"mrp":20,"margin":0,"wsp":0,"vat":0,"hbprice":0,"discount":0,"availableqty":0,"availableprice":0,"existingqty":0,"existingprice":0,"addedqty":0,"addedprice":0,"soldqty":0,"soldprice":0,"tags":"home"}]
}
Thank you.

Combining GET and POST as get and save in AngularJS service

I've got a service that gets details for a certain user profile:
.factory('UserDetails', function($resource, $rootScope) {
return $resource('../html/app/user_details.json', {}, {
query:
{
method: 'GET',
params: {},
isArray: true
}
});
})
I'm mocking the non-existent back-end with a plain JSON file for now.
I also need to be able to update the user profile - so I need to write a POST request (probably to the same end-point).
In my controller I currently use:
UserDetails.get({}, function(data) {
$scope.profile = angular.copy(data.user_details);
});
It would be elegant to be able to write
UserDetails.save({$scope.profile}, function(data){ /* .. */});
Is there a way to extend my UserDetails service to achieve this? How would I have to modify the service declaration?
You can use angular-mock for your purpouses.
See related Stackoverflow question

Resources