Saving subresource with Angular's $resource - angularjs

I have a factory defined which returns a $resource:
myApp.factory('Region', function($resource) {
return $resource(baseUrl + '/templates/:templateId/regions/:regionId', null, {
query: {
method: 'GET',
isArray: false
},
update: {
method: 'PUT'
}
});
});
As you can see, a region is a subresource of a template, and I've defined the endpoint as /templates/:templateId/regions/:regionId.
My issue comes when I want to save a new region. How do I specify the templateId to save the region to? Here's my snippet:
$scope.save = function() {
if ($scope.mode === 'edit') {
// TODO
} else {
Region.save($scope.region, function(success) {
$state.go('app.templateList')
});
}
};
In every other resource I have I've just used Model.save($scope.model);, I don't know how to specify other URL parameters and the Angular docs don't seem to cover it.

According the docs, non-GET (e.g. PUT) methods accepts following arguments
Resource.save([parameters], postData, [success], [error]).
Where parameters is a path params and it is optional, postData – body of the request. If you want to provide templateId, just add it as first argument:
Region.save({templateId: 'id'}, $scope.region, function(success) {
$state.go('app.templateList')
});

I've faced similar dillema. I thought about some generic convention where to create subresource X eg as a new element of a collection owned by some resource Y I would do
POST /api/Y/<yId>/X
then to access collection of X owned by Y:
GET /api/Y/<yId>/X
However for modifying or deleting subresource we could access subresource directly:
PUT /api/X/<xId>
DELETE /api/X/<xId>
to achieve above we could use $resource definition as
Subresource = $resource('/api/:parent/:parentId/subresource/:id',
{ id: '#id' },
{
'update': { method:'PUT' } // this is because Angular lacks PUT support
});
then we can use it like
var subresourceList;
Subresource.query({parent: 'Y', parentId: parentId },
function(result) {
// handle result here
subresourceList = result;
});
and after modifying single subresource object we can save it using
var subresource = subresourceList[0];
subresource.someProp = 'newValue';
subresource.$update()
with earlier subresource definition the $update will do PUT directly to /api/X/<xId> which is reasonable whenever subresource X object in terms of being modified has nothing to do with its owning Y.

Related

$resource PUT operation not extracting id

I can't seem to get an "id" to come through to the $resource function from the controller. Here is the offending code...
Controller:
$scope.update_user_extra = function () {
UserExtraResource.update($scope.user_extra_details, function (data) {
$scope.user_extra_details = data;
$scope.user_extra_details = {mobile:data.mobile,
landline:data.landline,
position:data.position,
notes:data.notes,
language:data.language};
});
};
Resource:
module.exports = function ($resource) {
return $resource('/api/user_extra/:id/', { id: '#_id' }, {
details: {method: 'GET', url: '/api/user_extra/details'},
update: {method: 'PUT'}
});
};
The GET works fine but the custom PUT returns:
http://127.0.0.1:8000/api/user_extra/ 404 (Not Found)
hardcoding the id like:
return $resource('/api/user_extra/1/', { id: '#_id' }, {
works fine. Any help is much appreciated!!
hmm ... changing this line to:
return $resource('/api/user_extra/:id/', { id: ̶'̶#̶_̶i̶d̶'̶ '#id' }, {
seems to have done it. Thank you very much!
If the default parameter value is prefixed with #, then the value for that parameter will be extracted from the corresponding property on the data object. For example, if the defaultParam object is {someParam: '#someProp'} then the value of someParam will be data.someProp.
In the above question, the PUT operation was trying to extract the property _id of the data object when the name of the property was actually id.
For more information, see AngularJS $resource API Reference.

actions in $resource in angularjs

I have a webapi back end with the following method in the ProductController :
[HttpGet]
[Route("api/product/FindName")]
public Product FindName(string name){
return name & "Hello "
}
I am trying to make use of $resource in the frontend.
var resource = $resource('api/product/:id', {});
resource.query() will return all items which are exposed in the server side using the GetALL() method. This works fine .
What exactly is the {action} in the $resource does ? I have seen examples for the POST, but what if is set
var resource = $resource('api/product/:id', {}, { FindName: { method: 'GET', params: { name: 'phone' } } });
will this call the method FindName in the backend ? or what exactly it does, I mean the parameter if I set the 'GET' in method.
I am calling as
resource.FindName({ name: 'phone' }, function () {
});
But the backend is not getting fired . i see the call that is being requested to the server from fiddler is
Demo/api/product?name=phone
The resource declaration is incorrect. It should be
var resource = $resource('api/product/:id', {}, { FindName: { method: 'GET', params: { id: 'phone' } } });
This defaults the id placeholder to value phone.
For invocation now you can do
resource.FindName({}, function () { //gets api/product/phone
});
or override id part
resource.FindName({id:'tablet'}, function () { //gets api/product/tablet
});
Resource has a built in GET function that should be able to be used without the need to define the extra FindName action that has been added to $resource.
If you changed the route on your webapi to be
[HttpGet]
[Route("api/product/{name}")]
public Product FindName(string name){
return name & "Hello "
}
Then you could use resource like this to get data back from this route.
var resource = $resource('api/product/:id', {}, {});
resource.get({ id: 'phone' }, function () {
});
If you wanted the name params to match on both you could change :id to :name and in the resource.get change id to name also.
I hope this helps.

$resource Custom Action Replaces my Scoped Variable with the Server Response

I am using a $resource with a custom action.
myApp.factory('User', [ '$resource', function($resource)
{
return $resource('/QuantumServer/users/:id.json',
{
id : '#id'
},
{
resetPassword :
{
method : 'POST',
url : '/QuantumServer/users/:id/resetPassword.json'
}
});
} ]);
I can retrieve my User objects no problem. The problem is that when I invoke the custom action, the value of my locally-scoped User object gets replaced with the server response. This is a problem because the server response is { success : true }, which causes my local object to loose all its field values.
$scope.resetPassword = function()
{
$scope.userBeingEdited.$resetPassword(
{}, function(value, responseHeaders)
{
alert('Password reset');
// The value of $scope.userBeingEdited has been replaced with the
// server response - how to stop this from happening?
});
};
I know that the RESTful philosophy states that e.g. a POST to a resouce would update that resource (on the server) and then return a copy of the updated resource. I undertand that this is how AngularJS $resouce.$save works. But must it really apply to my custom actions?
This is one workaround that I'm aware of, which causes a copy of the object to be updated which we then discard. Is this the most graceful way?
$scope.resetPassword = function()
{
angular.copy($scope.userBeingEdited).$resetPassword(function(value, responseHeaders)
{
alert('Password reset');
});
};

Can someone give me an example on how I can call $resource directly?

In my code I have:
var EntityResource = $resource('/api/:entityType', {}, {
postEntity: { url: '/api/:entityType/', method: 'POST' },
getEntity: { url: '/api/:entityType/:entityId', method: 'GET' },
putEntity: { url: '/api/:entityType/:entityId', method: 'PUT' },
deleteEntity: { url: '/api/:entityType/:entityId', method: "DELETE" },
getEntities: { url: '/api/:entityType/:action/:id', method: 'GET', isArray: true },
});
Then I am using the following to get data:
getProjects: function (
entityType,
deptId) {
var deferred = $q.defer();
EntityResource.getEntities({
action: "GetProjects",
entityType: entityType,
deptId: deptId
},
function (resp) {
deferred.resolve(resp);
}
);
return deferred.promise;
},
and the following to call getProjects:
entityService.getProjects(
'Project',
$scope.option.selectedDept)
.then(function (result) {
$scope.grid.data = result;
}, function (result) {
$scope.grid.data = null;
});
I think the intermediate function getProjects is not needed and I would like to directly use $resource.
Can someone give me some advice on how I could do this? I looked at the AngularJS documentation for $resource and it's not very clear for me.
$resource calls by default return empty arrays and then fill them up when the response is received. As mentioned in documentation
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
There are default 5 methods already defined on resource, get,save,query,remove,delete. You can directly call these rather than defining your own as you have done like postEntity, but the url template remains the same.
So once you define resource like this
var entityResource = $resource('/api/:entityType');
you can make calls like
var entity=entityResource.get({entityType:1},function(data) {
//The entity would be filled now
});
See the User example in documentation
If you want to return promise then you have to wrap the calls into your your service calls like you did for getProjects.
Update: Based on your comment, the definition could be
var entityResource = $resource('/api/:entityType/:action/:id')
Now if you do
entityResource.get({},function(){}) // The query is to /api
entityResource.get({entityType:'et'},function(){}) // The query is to /api/et
entityResource.get({entityType:'et',:action:'a'},function(){}) // The query is to /api/et/a
entityResource.get({entityType:'et',:action:'a',id:1},function(){}) // The query is to /api/et/a/1
Hope it helps.
$resource does expose $promise but it is on return values and subsequent calls.

Angular.js delete resource with parameter

My rest api accpets DELETE requests to the following url
/api/users/{slug}
So by sending delete to a specified user (slug) the user would be deleted. here is the service code:
angular.module('UserService',['ngResource']).factory('User', function($resource){
var User = $resource('/api/users/:id1/:action/:id2', //add param to the url
{},
{
delete_user: {
method: 'DELETE',
params: {
id1:"#id"
}
},
update: {
method: 'PUT',
params: {
id1:"#id"
}
}
});
return User;
});
I call the delete function via
user.$delete_user({id:user.id}, function(){}, function(response){});
However the request seems to be send to the wrong url.
/api/users?id=4
So the parameter is actually missing, as a result I get a 405 Method not allowed. Is there any chance to send the delete request in the style of my api?
params is an object of default request parameteres in your actions. If you want url parameters you have to specify them in the second parameter like this:
angular.module('UserService',['ngResource']).factory('User', function($resource){
var User = $resource('/api/users/:id1/:action/:id2', //add param to the url
{id1:'#id'},
{
delete_user: {
method: 'DELETE'
}
});
return User;
});
this works with either:
// user has id
user.$delete_user(function(){
//success
},function(){
// error
});
or
var data = {id:'id_from_data'};
User.delete_user({},data);
or
var params = {id1:'id1_from_params'};
User.delete_user(params);
I've made a plnkr-example - you have to open your console to verify that the DELETE requests are correct.
See parameterDefaults in the Angular resource documentation.
I had this problem for a while I was using a service to add / delete / update categories. While passing in params for get it worked fine but then when deleting it was giving me a ?id=1234 instead of api/resource/1234
I got around this by making the default param a string.
///Controller
Service.delete({categoryId:id}, function(resp){
console.log(resp)//whatever logic you want in here
});
//SERVICES
$resource('api/resource/:categoryId', {"categoryId":"#categoryId"}, {
query:{method:"GET"},
delete:{method:"DELETE"},
});
Should work and the resulting url will be, originally I had categoryId in the default params as a variable name.
api/resource/1234 etc
Just omit the '#' in the parameter
.factory('reportFactory', ['$resource', 'baseUrl', function ($resource, baseUrl) {
return $resource(baseUrl + '/keys/:id', {}, {
delete: { method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
params: {id: 'id'} }
})
}]);
this will give you:
http://localhost:8080/reports/api/keys/b8a8a8e39a8f55da94fdbe6c
without the question mark
If you want to delete a model, there's no need to add params (params does not work for DELETE anyway):
$resource('/users/:id').delete({id: user.id}, function(res) {
...
})
or
$resource('/users/:role/:id').delete({role: 'visitor', id: user.id});
I'm not sure if it's a bug of ngResource.

Resources