Angular.js: Why doesn't $resource have a PUT for updating model?

Created on 28 Oct 2014  ·  20Comments  ·  Source: angular/angular.js

Is it possible to expand the $resource with a create and a update action?

Example: 
{ 'get':    {method:'GET'},
  'save':   {method:'POST'},
  'create': {method:'POST'},
  'update': {method:'PUT'},
  'query':  {method:'GET', isArray:true},
  'remove': {method:'DELETE'},
  'delete': {method:'DELETE'} };

Is it also possible to override the save action to call create when id is null and update when id is set?

ngResource high public api not core inconvenient feature

Most helpful comment

var notes = $resource('/notes/:id', null, {
  'update': { method:'PUT' }
});

notes.prototype.$save = function() {
    if (this.id) {
        return this.$update();
    } else {
        return this.$create();
    }
};

All 20 comments

cuz others have different opinion about how it should be working and be named. how about this?

update -> PATCH (partial update)
replace -> PUT (replace the resource)
create -> POST (create a new resource)

if you would have scrolled down abit more you whould find this example about how to creating own method:

var notes = $resource('/notes/:id', null, {
  'update': { method:'PUT' }
});

@jimmywarting - your answer is a workaround and not a solution.

var notes = $resource('/notes/:id', null, {
  'update': { method:'PUT' }
});

notes.prototype.$save = function() {
    if (this.id) {
        return this.$update();
    } else {
        return this.$create();
    }
};

@jansoren your suggestions raise a lot of questions that would be hard to code in a generic manner, IMO. Ex.:

Is it possible to expand the $resource with a create and a update action?

  • what would be the difference between save and create?
  • it seems like different backends got different opinion on the HTTP verb being used for update. I saw PUT as well as POST and PATCH being used.

Is it also possible to override the save action to call create when id is null and update when id is set?

  • How would you identify id?

It seems to me like you are asking for generic solution for things that are approached differently by different backends so not sure what would be the best course of action here.

I see the challenges with implementing REST when it vary as much as it does. That said, I hope that AngularJS team provides guidance on how they want to solve it. At the present time the examples seems vague.

Responses to @pkozlowski-opensource :

  • the difference between save and create. save would POST when creating a resource and PUT when changing a resource. create would only use POST and create a resource.
  • I would use PUT when updating the whole resource, while I would use PATCH to update parts of a whole resource. PATCH I imagine would work well with Domain Driven Design.

With the help of @shlensky I have created this example:

resource.js

(function () {
    var resource = function ($resource) {
        var resource = {};
        resource.create = function(url) {
            return createResource($resource, url)
        }
        return resource;
    };

    var createResource = function($resource, url) {
        var resource = $resource(url, getParamDefaults(), getActions());
        resource.prototype.$save = function() {
            if (this.id) {
                return this.$update();
            } else {
                return this.$create();
            }
        };
        return resource;
    };

    var getParamDefaults = function() {
        var paramDefaults = {
            id:'@id'
        };
        return paramDefaults;
    };

    var getActions = function() {
        var actions = {
            'create': {method:'POST'},
            'update': {method:'PUT'},
            'all':    {method:'GET', isArray:true}
        };
        return actions;
    };

    angular.module('myModule').factory('resource', resource);
}());

CreditCard.js

(function () {

    var CreditCard = function (resource) {
        return resource.create('card/:id');
    };

    angular.module('myModule').factory('CreditCard', CreditCard);
}());

myController.js

(function () {
    var myController = function ($scope, CreditCard) {
        // get
        var creditCard = CreditCard.get({id:'abc123'}, function() {
            console.log(creditCard);
        });

        // all
        var creditCards = CreditCard.all(function() {
            console.log(creditCards);

            // update
            var creditCard = creditCards[0];
            creditCard.name = 'changed';
            creditCard.$save(function() {
                console.log(creditCard)
            });
        });

        // create
        var creditCard = new CreditCard();
        creditCard.name = 'new';
        creditCard.$save(function() {
            console.log(creditCard)
        });
    };
    angular.module("myModule").controller("myController", myController);
}());

how is the client going to know whether it's creating or updating? it doesn't really know. really, as a developer of the client, you have to know yourself whether you need to create or update.

@caitp this is the exact question I was going to ask :-)

To know the difference between create / update we would have to introduce some kind of ID notation which complicates matters further. And assume that an object is not saved if doesn't have id filled in.

@jansoren I think that the current guidance from the team is clear: $resource comes with defaults that make sense for most RESTful back-ends. For cases where the scene is not so clear there are extension points in form of custom / additional methods. $resource is very extensible and frankly it is so easy to build $resource like wrapper based on $http that it doesn't make sense to capture all the possible use-cases as part of the $roesoure.

IMO $resource, by default, should have only things that are common to large number of backends / scenarios and leave enough flex points so people can extend it for more "exotic" backends / scenarios (or for situations where there is no consensus).

it would be interesting if the method parameter could be a function, so that it could dynamically determine the right mode to use, though.

@caitp - It's covered in my example above:

 resource.prototype.$save = function() {
            if (this.id) {
                return this.$update();
            } else {
                return this.$create();
            }
        };

@pkozlowski-opensource - I'm just saying it still has potential to improve. And i find it odd that the ngResource documentation refer to wikipedia that states; PUT for updating a resource and POST for creating a resource. Why are not PUT implemented as a standard in ngResource? (back to my initial question)
Developers tend to accept the config way, when it could be simpler.

Well the contrast between PUT and POST isn't specifically whether you are creating or updating, but whether you are editing a resource or a resource's child. From RFC 2616:

The PUT method requests that the enclosed entity be stored under the supplied Request-URI.

...versus...

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI ...

For most RESTful systems, this ends up being create and update, but it isn't a guarantee, and that's why I'm a big fan of Angular's current neutral term save for POST, which doesn't have strong connotations either way. I also agree that adding a create function that also calls POST wouldn't be helpful because, as others have noted, its communication with the server would not be distinguishable from save.

_However_, I am a big fan of including a PUT method: PUT is by definition idempotent (see section 9.1.2 above) so multiple copies of a request should generally have the same result as a single copy. POST does not have this property, and frequently servers will interpret multiple copies of a request independently and create duplicate resources for each of them. In my mind, this is enough to merit inclusion, as there should be at least one non-duplicative editing route in any "standard" REST toolbox.

The two back-end REST resource implementations I use include a PUT:

Plus, the hundreds of routing libraries out there nearly all consider PUT to be an essential core method.

Granted, it isn't _broken_ not to have it, but it feels odd to be using all the defaults and then having to add my own PUT to every $resource I define.

@tdhsmith
I agree with you, it would be very interesting to have a PUT method, I would call him "update", and a PATCH would be "partial_update"

I took these names from DJango REST Framework, the generic views have the two methods:

def update(self, request, pk=None):
pass

def partial_update(self, request, pk=None):
pass

I think the correct implementation of a back-end REST must have a method PUT that perform updates, but i know that not everybody implements it.
My current implementations in ASP.NET don't have the PUT method because, since c# is strongly typed, it is easier to implement control flow in the back-end

It seems pretty typical to require a PUT method in v1 of any data app, though, right? +1 for finding some convention that allows standard inclusion of these few lines of code in angular.

See http://kirkbushell.me/angular-js-using-ng-resource-in-a-more-restful-manner/ for one pretty clean way to add your custom methods to $resource.

@sjatkins For me, that's more of a workaround.


I also agree that $resource should have a default option for PUT, it seems weird to have to configure it manually every time you need to use PUT to update a record, which is pretty common, IMO. A default option for PATCH can be discussed even though it's a bit more uncommon, but at least one for PUT is reasonable.

Also, people were asking what would be the difference between $save and $create. Well, maybe the same difference between $delete and $remove, i.e. none? I see no problems with aliases, even ngResource has one already.

Resource is suppose to based off of Rest, so why doesn't it just have the http restful routes baked in...

Example:

{
    'get':    {method:'GET'},
    'post':   {method:'POST'},
    'update': {method:'PUT'},
    'query':  {method:'GET', isArray:true},
    'delete': {method:'DELETE'}
};

Because different backends have different opinions about the verbs that should be used.
$resource provides a generic solutionand is easily extensible.

Basically, you can set the default actions using $resourceProvider.defaults.actions.
Not sure if leaving this undocumented was intentional or just an oversight.

Well fancy that, I'll give resource another go then. I love extensible components!

The docs have been updated with clearer info on default actions. This should allow easy configuration of PUT if you want it.

Was this page helpful?
0 / 5 - 0 ratings