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.
Related
I am using ngresource as follows but unfortunately I am unable to access the $update, $remove and $save methods in this way. What am I doing wrong?
angular.module('myApp.services').factory('Entry', function($resource) {
return {
method1: $resource('/api/entries/:id', { id: '#_id' }, {
update: {
method: 'PUT' // this method issues a PUT request
}
}),
method2: $resource('/api/entries2', {}, {
})
});
// not working: Entries is not a function at Scope.$scope.save
var entry = new Entries({});
entry.method1.$update();
entry.method1.$save();
entry.method1.$delete();
On the other hand, this works:
angular.module('myApp.services').factory('Entry', function($resource) {
return $resource('/api/entries/:id', { id: '#_id' }, {
update: {
method: 'PUT' // this method issues a PUT request
}
});
});
var entry = new Entries({});
entry.$update();
entry.$save();
entry.$delete();
So your second example doing $resource('http://example.com/resource.json') is the correct usage of that construction, while the first one is not.
After executing var entry = new Entries({}); you get entry as factory instance in your controller, which has available actions that you've defined for it.
UPD
You can have multiple resources in the service - https://stackoverflow.com/a/17163459/405623. In your example you've just missed the ['ngResource'] DI in your module.
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.
I've got a problem with service configuration. I want to display one user by this function:
$scope.findOne = function() {
$scope.user = Users.get({
userId: $stateParams.userId
});
};
But I am in trouble with User service :( I don't know, how should I change my the code to avoid angular error:
Error in resource configuration for action object. Expected response
to contain an array but got an {2}
Here is a code of my actual working service (without function findOne working of course:))
'use strict';
angular.module('users').factory('Users', ['$resource',
function($resource) {
return $resource('users', {}, {
update: {
method: 'PUT'
},
remove: {
method: 'DELETE',
url: 'users/:id',
params: {id: '#_id'}
}
});
}
]);
At a guess, I'd say your users API endpoint is expecting /users/:userId for GET requests. Your code at the moment will request /users?userId=nnn. You need to add an action for get with the ID in the URL, eg
return $resource('users', {id: '#userId'}, {
get: {
method: 'GET',
url: 'users/:id',
isArray: false
},
// etc
You can also make users/:id the default URL as long as it doesn't interfere with your other action configurations.
Here's my resource:
.factory('Posting', ['$resource', function ($resource) {
return $resource('api/Postings/:action/:arg', {}, {
findByParent: { method: 'GET', params: { action: 'parent', arg: '#guid' }, isArray: true },
findByReference: { method: 'GET', params: { action: 'reference', arg: '#reference' }, isArray: true }
});
}]);
In my controller I'm using my resource as this:
Posting.findByParent({ guid: parent_guid },
function (success) {
...
},
function (error) {
...
});
This returns the URL /parent?guid=0ff646e9-4397-4654-b8d2-118c6258023a
However, using my resource like this:
Posting.findByParent({ arg: parent_guid },
function (success) {
...
},
function (error) {
...
});
Gives me the correct URL: /parent/0ff646e9-4397-4654-b8d2-118c6258023a
I thought the point with using an '#' was to give parameters better names?
I'm also wondering if I still should use $resource even tho my API isn't really RESTful.
Is it better to give my custom (unRESTful) functions their own URL? Something like:
findByParent: { method: 'GET', url: 'api/Postings/parent/:guid', params { guid: '#guid' }, isArray:true }
By default, if you define a parameter on the path (like you did with arg), and you pass in an object that has a matching key, like in the second example, that key will be used to resolve the path.
If however, there are no matching parameter, the keys of the object passed in will resolve to query parameters, like in the first example.
To set custom default resolves, you need to specify them in the second argument to resource, like this:
.factory('Posting', ['$resource', function ($resource) {
return $resource('api/Postings/:action/:arg',
{
action: '#action',
arg: '#guid'
},
{
findByParent: { method: 'GET', params: { action: 'parent' }, isArray: true },
findByReference: { method: 'GET', params: { action: 'reference' }, isArray: true }
});
}]);
This should make action resolve to what is specified in findByParent and findByReference, and arg to whatever value is passed in for key guid.
You could experiment with setting an # in the respective methods 'guid' property, but for your usecase, it does not seem to be necessary.
to answer your second question: you can specify several parameter controllers on a single path element (level). The only condition is that you don't use / specify resolutions for more than one of them in a single method. That is, you could do api/Postings/:action:anotherController/:arg, as long as you would specify resolutions for :action and :anotherController in separate methods.
Please find this awesome post by Ben Nadel http://www.bennadel.com/blog/2433-using-restful-controllers-in-an-angularjs-resource.htm with an example use
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.