AngularJS $resource object doesn't provide an idempotent update method? - angularjs

I've really be trying to wrap my head around this as much as possible, but having a very difficult time doing so. Maybe I'm missing the obvious.
Given a typical REST API (with an idempotent update method and a create method):
http://www.domain.com/api/clients GET // returns all clients
http://www.domain.com/api/clients POST // create a new client
http://www.domain.com/api/clients/:id GET // returns single client
http://www.domain.com/api/clients/:id PUT // updates a single client - idempotent
http://www.domain.com/api/clients/:id DELETE // delete single client
If I create a standard resource with the following URL:
Client = $resource("http://www.domain.com/api/clients/:id")
Then I automatically get (where Client is the $resource and client is the returned entity):
Client.get()
Client.query()
Client.save()
client.$save()
client.$remove/delete()
The problem I have is by default there is no PUT method to save (typically used to identify idempotent updates).
Am I misunderstanding something or is this a deficiency in Angular's API? I would have expected the $save() to use a PUT and not a POST. The way it is currently structured, I have to create my own $update() method definition and then rely on the developer not to accidentally use the $save() method.
Am I structuring my API incorrectly? Should the REST API be structured differently?

You can simply specify the method in your resource like :
app.factory('someFactory', ['$resource', function($resource) {
return $resource('/api/:id', {
id: '#id'
}, {
update: {
method: 'PUT'
},
get: {
method: 'GET'
}
});
}]);
but I totally agree with $save being an odd verb for create and not update. This guy does too and it looks like he made a way to dual purpose the save by simply extending the object and checking for an id.

Related

Saving an ngResource with additional query string parameters

I have a resource in my API called /foos. I can create a new Foo by making a POST request to this URL.
In the client I use Angular's ngResource to deal with API calls. Insertion is easy:
var newFoo = new Foo({param1: 'value1'});
newFoo.$save();
So far, so good. The API returns the following:
{id: 1, param1: "value1"}
Now, here's the good part. For the sake of simplicity my API allows to specify which related entities should be returned in the response by means of a query string param, with. So:
GET /foos/1?with=relatedEntity
Would return
{id: 1, param1: "value1", relatedEntity: { ... }}
This is also accepted in POST requests. I'd like to create a Foo and have it returned back with some related entities so that I can use them straight away in my app without the need to perform another request:
POST /foos?with=relatedEntity
However, I've been unable to achieve this using standard the ngResource $save() function.
Can this be done at all?
My bad.
Our client extends the base $resource factory and overrides the $save() function, but this implementation was not propagating all the required parameters down the call chain, so no URL params were being sent.

Backbone model urlRoot share the middle part of api

I want to fetch an api and the format is like apps/[:id]/result.
For most of the example or documentation about setting the urlRoot is based on having the same root url.
But my problem is the api share the same root and the end of the api name.
I try to use below code, but it doesn't work.
var Model = Backbone.Model.extend({
urlRoot: "/apps"
})
var model = new Model({id: 123123 + "/content"]}) //123123 just fake id
Does it any way to change only the middle of the id?
Or in this situation, use urlRoot is inappropriate?
Backbone's persistence methods, which are built around url and urlRoot, are designed for RESTful persistence. Each method is designed to communicate with a server using well-defined semantics (GET /path means list, PUT /path/:id means update, etc).
Chaning the meaning of id is a bad idea because you depart break those semantics, meaning that a) your model will not work with create, update or delete REST operations, and b) your code will become difficult to understand since id will no longer be a descriptive variable name. Backbone uses id frequently in Models and Collections, so it would be a very bad idea to change its meaning.
If you are not using a REST API, or you have a particular operation that does not fit that paradigm (like searching), it is best to implement your own methods to make custom HTTP requests.
In your case, something like this should work as you expect:
var Model = Backbone.Model.extend({
urlRoot: "/apps",
fetchResult: function() {
return $.ajax({
url: this.url() + '/result'
}).then(function(response) {
// do something with response
// return response, or the result of your processing,
// for downstream promise handlers
return response;
}, function() {
console.error('fetchResult failed');
})
},
})
With this approach the RESTful URLs work as designed and you don't lose the semantics of id.

Play 2.3/Angular JS $resource/routing issue

So I'm trying to AJAX a single solr doc from my results list to a "doc view" view. I'm trying to use AngularJS to AJAX to my view render method and display the doc that way, but I can't seem to get the angular to work and I'm not sure I'm doing things correctly on the Play side either. Would you at least be willing to tell me if what I'm trying to do will work? The Angular error comes from the docText.text(); call. Here is my code:
Angular controller code:
var docText = $resource("http://localhost:9000/views/full-doc-text.html", {
text: {method: 'PUT'}
});
$scope.handleViewText = function(value) {
docText.text({doc: value});
}
Java code:
public static Result viewText() {
JsonNode json = request().body().asJson();
//do stuff here
return ok(viewtext.render(json));
}
route:
GET /views/full-doc-text.html controllers.Application.viewText()
I see three problems with the code above;
1.The definition of docText resource is not correct. if your read the angularjs manual here you'll see that $resource has 4 parameters. First one is resource url, second is parameter defaults, third one is custom actions and forth one is resource options where last three of them are optional. In your code you pass custom actions as the second parameter, which should be the third. And since you don't have any parameters in your resource url second parameter must be null. So first correction is:
var docText = $resource("http://localhost:9000/views/full-doc-text.html", null, {
text: {method: 'PUT'}
});
2.You define your text action's HTTP method as PUT however in your routes file you are handling GET requests for your desired action. You should change your route definition as:
PUT /views/full-doc-text.html controllers.Application.viewText()
3.PUT method is usually used for update operations when implementing a RESTFULL service. In your case you don't seem to be updating anything. So I suggest to use POST method just for convention.

How do you update a cached resource in an angular service?

What is a good pattern for updating angular data from a ngResource service that has been cached?
I been trawling posts like this one [1]: How to refresh / invalidate $resource cache in AngularJS, but would be good to hear from angular experts on the right approach for this specific (but pretty general) scenario.
I am looking for a general pattern here. Both in understanding and in implementing angular - I am a novice at it.
I have a pretty standard ngResource service that has a very standard query method, and a custom put method update.
myServices.factory('ThingsService', [
'$resource',
function ($resource) {
return $resource('/api/things/:id', { id: '#id' }, {
query: { method: 'GET', isArray: true, cache: true },
update: {method: 'PUT', cache: true },
});
}]);
I am using it from a controller like this:
$scope.things = ThingsService.query(function (x) {
// must assign these only once data is loaded
$scope.allCount = x.Things.length;
$scope.incomingCount = $filter('filter')(x.Things, { State: 'incoming' }).length;
});
So far so good. The data is returned just fine, and it renders nicely in a dashboard view.
We support in-place-editing and the user can edit the data right there in the dashboard list.
First take a shadow copy of the thing using angular.copy(...) so that we can support buffering of the changes for the user. (just like a dialog box does for a user). Then when they confirm their changes, we call with the shadow copy:
ThingsService.update({ id: currentThing.Id }, { Data: currentThing.Data }, function () {
//TODO: now, if this PUT succeeds,
//I want to update the value of $scope.things array to reflect the changes,
//without going to back to the server for the whole array.
});
This correctly PUTS the changes to the server, which returns an updated thing, but the dashboard view which is bound to the query method is not updated auto-magically. Was kind of hoping angular and the ThingsService and its cache would take care of that for me somehow, you know by updating the cached data. Since the service should know that I just updated one of the items that the service serves up.
So to avoid going all the way back to the server we have told the ThingsService to cache its results, which is a good start. But how are you supposed to update the changed thing in the cached data?
Is there a standard pattern for this kind of update with a ngResource service?
Preferably I wouldn't have to mess with the cache directly. I should not even care that it is cached or how. I just want $scope.things to reflect the posted changes changes.

Backbone.Model.save and don't update client

I want to call save on a Backbone model and have it write data to the server, but not update the client. How do I do this?
To clarify: when Backbone saves a model, it sends all the data to the server, and then retrieves the data from the server and updates it on the client. This second step is what I want not to happen.
To clarify further: The model ('parent' model) has an attribute which is itself a model ('child' model); when it's saved to the server, this child model is converted to JSON. When the parent model updates after the save, the attribute that previously contained a reference to the child model is replaced with the parsed JSON object of the child model that was saved. This is what I need not to happen.
When the data is initially pulled from the server, the parent model "reconstitutes" that object into an appropriate child model, but this is an expensive process and there is no reason to re-do it every time save fires on the parent model, since the child model will never change.
It sounds like you do not want to parse your model when you receive the response from the server on a model.save
You can try something such as:
model.save(attributes,{
success: function() { ... },
parse : false // This will be passed to your parse function as an option
});
You would have to set-up your parse function in your corresponding model as follows:
parse: function(resp,options) {
// don't update model with the server response if {parse:false} is passed to save
if (options && !options.parse) return;
/ ... rest of your parse code ... /
Backbone currently defaults options.parse to true. Here is a short-discussion on the topic.
As discussed in that thread, perhaps you want to consider why you do not want want to update the server response to the client. There may be a cleaner way to achieve the results you desire.
Depending on how/what your server setup is, all you really have to do is issue a regular AJAX request. This is exactly what backbone does in the background so you'll just bypass the client side logic.
You could do this with native JavaScript, but I'm fairly sure you have some other library in use that can make things much easier.
For the completeness of this answer, I'll give an example with jQuery:
$.ajax({
type: "POST",
url: "http://same.as.your.model",
data: { "the" : "model" },
dataType: "JSON",
success: function(){
// once the request has returned
}
});
The $.ajax function also has some additional functionality, and you can read about it in the jQuery docs.
On client you mean Views? If you want to save your model but not render your views which happens since save will trigger a change event, you should call save with option silent:true, or set a custom option like dontchange:true when calling save and check it in when handling change. I prefer the custom option, because silent has side effects (at least in my version of backbone 1.0.0)
a little code:
when you save:
model.save({},{dontchange: true});
you install your event listeners in the view:
this.listenTo(model, 'change', function(model, options){
if (options.dontchange)
return;
this.render();
});
I ran into same problem. model.save(attrs,{patch:true, parse:false}) really did not invoke parse method but model was still merged with server response.
It is not elegant, but this worked for me:
model.clone().save(attrs,{patch:true})
I believe it's best to avoid this situation by clean REST api design.

Resources