Create a generic angular resource action for more entities - angularjs

I'm using angular resource with different entities and I have to call, for each of these entities, some custom methods with custom urls.
For example:
var actions = {
create: {
method: 'POST'
},
customMethod: {
method: 'PUT',
url: '/api/entity1/:id/custom',
params: {
id: '#id'
},
}
}
$resource('/api/entity1/:id', null, actions);
$resource('/api/entity2/:id', null, actions);
$resource('/api/entity3/:id', null, actions);
In this way, obviusly, customMethod will work only on entity1 because the url is not generic.
How can I create a custom action without defining urls, as I did on create action?
Thanks

you can use multiple variables when using $resource. replace
url: '/api/entity1/:id/custom'
with
url: '/api/:entity/:id/custom'
and use the appropriate params when calling the $resource function.

Add an action param for custom method and url:
var actions = {
create: {
method: 'POST'
},
customMethod: {
method: 'PUT',
params: { action: 'custom' },
}
}
$resource('/api/entity1/:id/:action', null, actions);
$resource('/api/entity2/:id/:action', null, actions);

Related

Angular $resources Cancel requests : $q

I'm trying to understand...
How can I implement a requests cancell for this kind of services.
I was reading, that I shoud use $q.defer()
angular.module('App').service('TService', function ($resource, portal) {
return $resource(portal.getUrlServer() + 'api/T/:id', { id: '#Id' }, {
T_GET: {
method: 'GET',
params:{
id_turno: '#id_turno',
},
url: portal.getUrlServer() + 'api/T/T_GET/'
},
G_GET_Turno: {
method: 'GET',
params: {
id_tramite_relevado : '#Id_Tramite_Relevado'
},
url: portal.getUrlServer() + 'api/T/G_GET_Turno/'
},
});
What I want to do is when any method is called twice, I just want to let run the last called and cancel the other requests.
From AngularJS docs:
If an action's configuration specifies that it is cancellable, you can cancel the request related to an instance or collection (as long as it is a result of a "non-instance" call).
// ...defining the `Hotel` resource...
var Hotel = $resource('/api/hotel/:id', {id: '#id'}, {
// Let's make the `query()` method cancellable
query: {method: 'get', isArray: true, cancellable: true}
});
// ...somewhere in the PlanVacationController...
...
this.onDestinationChanged = function onDestinationChanged(destination) {
// We don't care about any pending request for hotels
// in a different destination any more
this.availableHotels.$cancelRequest();
// Let's query for hotels in '<destination>'
// (calls: /api/hotel?location=<destination>)
this.availableHotels = Hotel.query({location: destination});
};

Angular $resource POSTing entire object, not just properties passed to it

I'm using Angular's $resource to interface with an API, and creating custom methods on that resource. One of these methods is a POST, and when I attempt to use it, it's sending the entire resource, not just the properties I'm attempting to post to the API. I don't think this is the intended behavior of the $resource service, but then, I might be missing something.
Here's the code:
The service:
angular.module('adminApp')
.factory('Framework', function($resource) {
return $resource('/api/frameworks/:id', {id: '#id'}, {
'update': {
method: 'PUT'
},
'getRequiredLicenses': {
method: 'GET',
url: '/api/frameworks/:id/required_licenses',
isArray: true
},
'addRequiredLicenses': {
method: 'POST',
url: '/api/frameworks/:id/required_licenses'
},
'removeRequiredLicense': {
method: 'DELETE',
url: '/api/frameworks/:id/required_licenses/:license_id'
}
});
});
Where I'm calling it:
scope.addLicensesToFramework = function() {
scope.framework.$addRequiredLicenses(null, {
required_licenses: Object.keys(scope.selectedLicenses) // returns an array of ints
});
}
(Note that this is in a directive. scope.framework is the instance of the framework resource)
When this request is sent, here's what's being included in the payload:
My intention is to only pass {'required_licenses': [12345,1236]} in the payload, and I can't seem to figure out why it's sending the entire resource as the body. (It's, in fact, not sending this at all, only the original resource)
Any insight would be really helpful, thanks!
Try calling it like this:
scope.addLicensesToFramework = function() {
scope.framework.$addRequiredLicenses({
required_licenses: Object.keys(scope.selectedLicenses),
id: 1234
}, function(resp){ console.log(resp) });
}
Also notice that I included the id in the parameters object.. you'll probably need that.

Building one object with only one $resource

I'm building a fullrest app with $resources, I read about It but I didn't find any answer.
return $resource('/rings', {}, {
getRings: {
method: 'GET',
isArray: true
},
patchRing: {
method: 'PATCH',
params: {
slug: '#slug'
}
}
}
Get Rings is doing ok, but How can I "patchRing"? I mean I want to PATCH for endpoint: /rings/:slug Is this possible? or Do I need another $resource for that (like next one)?
return $resource('/rings/:slug', { slug: '#slug'}, { [...]
EDIT: I don't want the "PATCH" like this /rings?slug=lorem just /rings/lorem
EDIT 2: My point is only the endpoint construction... because $resource is requesting to /rings?slug=lorem instead of build request like /rings/lorem
Try this in your config
$resourceProvider.defaults.stripTrailingSlashes = true;
This will not end as /
Try putting the param in the $resource definition instead of the PATCH method:
var Ring = $resource('/rings/:slug', {slug: '#slug'}, {
getRings: {
method: 'GET',
isArray: true
},
patchRing: {
method: 'PATCH',
}
});
ring = Ring.patch({slug: 'lorem'}, function() { ... });
If the slug parameter is not passed, then it is not added to the HTTP path.

Use of '#' in angular resource - am I doing it wrong?

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

Invalidate $resource Cache After Post Request

I am using $resource and caching the results of get requests. My problem is that, after post requests, the cache is not being invalidated.
Here is the return value from the service:
return $resource('http://url.com/api/url/:id', {}, {
'query' : {
method : 'GET',
isArray:true,
cache : true
},
'get' : {
method : 'GET',
cache : false
}
})
Here is the save method I am using inside my controller. As you can see, I'm using the callback on the post request to recalculate the query/list of nouns.
var newNoun = new Noun($scope.noun);
newNoun.$save(function(x) {
$scope.nouns = Noun.query();
});
I would like to invalidate the cache after calling post or another non-get method. How could I do this? Is this already built into $resource or do I need to implement it on my own?
You could create a wrapper service to do the caching like you want, for example:
app.factory('cachedResource', function ($resource, $cacheFactory) {
var cache = $cacheFactory('resourceCache');
var interceptor = {
response: function (response) {
cache.remove(response.config.url);
console.log('cache removed', response.config.url);
return response;
}
};
return function (url, paramDefaults, actions, options) {
actions = angular.extend({}, actions, {
'get': { method: 'GET', cache: cache },
'query': { method: 'GET', cache: cache, isArray: true },
'save': { method: 'POST', interceptor: interceptor },
'remove': { method: 'DELETE', interceptor: interceptor },
'delete': { method: 'DELETE', interceptor: interceptor },
});
return $resource(url, paramDefaults, actions, options);
};
});
Then replace any $resource with cachedResource.
Example plunker: http://plnkr.co/edit/lIQw4uogcoMpcuHTWy2U?p=preview
While #runTarm's answer above is great, it does not allow actions to be easily customized from the inheriting service, e.g. the following would not be possible:
app.factory('Steps', function (CachedResource) {
return CachedResource('/steps/:stepId', {}, {
save: { method: 'POST', params: { stepId: '#stepId' } }
});
});
In this case, this definition of save would be replaced by the one present in CachedResource.
Solution
But it can be fixed easily from Angular 1.4 by replacing
actions = angular.extend({}, actions, {
with
actions = angular.merge({}, actions, {
so that both objects are deep-merged.
Even better solution
In the above scenario, action options defined in CachedResource would be preferred over custom configuration in inheriting services. To fix that, switch the order of arguments passed to merge:
actions = angular.merge({}, { /* default options get, query, etc. */ }, actions);
With this solution, the following will work as expected (i.e. use DESTROY instead of default DELETE when calling remove):
app.factory('Steps', function (CachedResource) {
return CachedResource('/steps/:stepId', {}, {
remove: { method: 'DESTROY' }
});
});
$resource is using the default cache for $http.
You can access it using: $cacheFactory.get('$http')
You can remove a key value pair, using the returned caches remove({string} key) method.
E.g.:
var key = '...the key you want to remove, e.g. `/nouns/5`...';
$cacheFactory.get('$http').remove(key);

Resources