Partial Updates (aka PATCH) using a $resource based service?
Asked Answered
R

3

10

We're building a web application using Django/TastyPie as the back-end REST service provider, and building an AngularJS based front end, using lots of $resource based services to CRUD objects on the server. Everything is working great so far!

But, we would like to reduce the amount of data that we're shipping around when we want to update only one or two changed fields on an object.

TastyPie supports this using the HTTP PATCH method. We have defined a .diff() method on our objects, so we can determine which fields we want to send when we do an update. I just can't find any documentation on how to define/implement the method on the instance object returned by $resource to do what we want.

What we want to do is add another method to the object instances, (as described in the Angular.js documentation here) like myobject.$partialupdate() which would:

  1. Call our .diff() function to determine which fields to send, and then
  2. Use an HTTP PATCH request to send only those fields to the server.

So far, I can't find any documentation (or other SO posts) describing how to do this, but would really appreciate any suggestions that anyone might have.

thank you.

Robenarobenia answered 8/10, 2013 at 21:1 Comment(0)
E
4

We implemented $patchusing ngResource, but it's a bit involved (we use Django Rest Framework on the server-side). For your diff component, I'll leave to your own implementation. We use a pristine cache to track changes of resources, so I can poll a given object and see what (if any) has changed.

I leverage underscore's _.pick() method to pull the known fields to save off the existing instance, create a copy (along with the known primary key) and save that using $patch.

We also use some utility classes to extend the built-in resources.

app.factory 'PartUpdateMixin', ['$q', '_', ($q, _) ->

    PartUpdateMixin = (klass) ->
        partial_update: (keys...) ->
            deferred = $q.defer()
            params = _.pick(@, 'id', keys...)
            o = new klass(params)
            o.$patch(deferred.resolve, deferred.reject)
            return deferred.promise
]

Here's the utility classes to enhance the Resources.

app.factory 'extend', ->
    extend = (obj, mixins...) ->
        for mixin in mixins
            obj[name] = method for name, method of mixin
        obj

app.factory 'include', ['extend', (extend) ->
    include = (klass, mixins...) ->
        extend klass.prototype, mixins...

    return include
]

Finally, we can enhance our Resource

include TheResource, PartUpdateMixin(TheResource)
resourceInstance = TheResource.get(id: 1234)
# Later...
updatedFields = getChangedFields(resourceInstance)
resourceInstance.partial_update(updatedFields...)
Elwandaelwee answered 9/10, 2013 at 6:37 Comment(9)
Interested in how you did both of these. 1- How did you do the pristine cache? Do you basically have a part of the resource that contains the original data, and when $patch is called, just do a compare? 2- How did you implement $patch?Sorbose
1- You just need to create a copy of the resources attributes on load (or creation) that you can compare during an update (the getChangedFields() in the example). 2- To implement the patch on the client, you just add an extra method on the $resource definition patch: { method:'PATCH' }. As for the server, that's handled by Django Rest Framework in our case, so its backend dependent.Elwandaelwee
I know about the $resource definition, but doesn't it always send all of the props, even if you override with {method: 'PATCH'}? How did you get $resource to send just the changed fields?Sorbose
And for loading pristine fields on load/creation: did you use an angular event, or some custom to catch it?Sorbose
No worries about the server; lots of REST APIs available, and I have my own.Sorbose
I calculated the changed field (the getChangedFields() implementation) and then called partial_update() with the updates that makes the API call with the PATCH method.Elwandaelwee
I think I get it. Your partial_update doesn't submit just the changes; it creates a new instance of the same class with just the properties you want to update, and then it still sends everything with the PATCH, but everything is a subset of what was on the original instance. Is that it?Sorbose
It doesn't send everything, just the updated fields. The PATCH implementation on the server just merges the updated fields with the rest of the object stored on the server. So, Resource.partial_update({id: 1, something: "updated"}) would just updated the value of the something field in the database.Elwandaelwee
But you aren't actually doing $patch on the original object? You're using partial_update to get the changed fields, then create a new object with just those fields, then doing $patch()?Sorbose
S
14

I would suggest using

update: {
    method: 'PATCH',
    transformRequest: dropUnchangedFields
}

where

var dropUnchangedFields = function(data, headerGetter) {

    /* compute from data using your .diff method by  */
    var unchangedFields = [ 'name', 'street' ];

    /* delete unchanged fields from data using a for loop */ 
    delete data['name'] ;
    delete data['street'];

    return data;

}

PS: not sure from memory, whether data is a reference to your resource of a copy of it, so you may need to create a copy of data, before deleting fields

Also, instead of return data, you may need return JSON.stringify(data).


Source (search for "transformRequest" on the documentation page)

Skittish answered 28/4, 2014 at 15:4 Comment(1)
If it helps others: We just wanted to send one or two specific fields instead of sending all of them. We're using Lodash. To just send the publish field, we can use the following beautifully simple transformRequest function: var dropAllButPublish = function(data, headerGetter) { return JSON.stringify(_.pick(data, "publish")); }Give
E
4

We implemented $patchusing ngResource, but it's a bit involved (we use Django Rest Framework on the server-side). For your diff component, I'll leave to your own implementation. We use a pristine cache to track changes of resources, so I can poll a given object and see what (if any) has changed.

I leverage underscore's _.pick() method to pull the known fields to save off the existing instance, create a copy (along with the known primary key) and save that using $patch.

We also use some utility classes to extend the built-in resources.

app.factory 'PartUpdateMixin', ['$q', '_', ($q, _) ->

    PartUpdateMixin = (klass) ->
        partial_update: (keys...) ->
            deferred = $q.defer()
            params = _.pick(@, 'id', keys...)
            o = new klass(params)
            o.$patch(deferred.resolve, deferred.reject)
            return deferred.promise
]

Here's the utility classes to enhance the Resources.

app.factory 'extend', ->
    extend = (obj, mixins...) ->
        for mixin in mixins
            obj[name] = method for name, method of mixin
        obj

app.factory 'include', ['extend', (extend) ->
    include = (klass, mixins...) ->
        extend klass.prototype, mixins...

    return include
]

Finally, we can enhance our Resource

include TheResource, PartUpdateMixin(TheResource)
resourceInstance = TheResource.get(id: 1234)
# Later...
updatedFields = getChangedFields(resourceInstance)
resourceInstance.partial_update(updatedFields...)
Elwandaelwee answered 9/10, 2013 at 6:37 Comment(9)
Interested in how you did both of these. 1- How did you do the pristine cache? Do you basically have a part of the resource that contains the original data, and when $patch is called, just do a compare? 2- How did you implement $patch?Sorbose
1- You just need to create a copy of the resources attributes on load (or creation) that you can compare during an update (the getChangedFields() in the example). 2- To implement the patch on the client, you just add an extra method on the $resource definition patch: { method:'PATCH' }. As for the server, that's handled by Django Rest Framework in our case, so its backend dependent.Elwandaelwee
I know about the $resource definition, but doesn't it always send all of the props, even if you override with {method: 'PATCH'}? How did you get $resource to send just the changed fields?Sorbose
And for loading pristine fields on load/creation: did you use an angular event, or some custom to catch it?Sorbose
No worries about the server; lots of REST APIs available, and I have my own.Sorbose
I calculated the changed field (the getChangedFields() implementation) and then called partial_update() with the updates that makes the API call with the PATCH method.Elwandaelwee
I think I get it. Your partial_update doesn't submit just the changes; it creates a new instance of the same class with just the properties you want to update, and then it still sends everything with the PATCH, but everything is a subset of what was on the original instance. Is that it?Sorbose
It doesn't send everything, just the updated fields. The PATCH implementation on the server just merges the updated fields with the rest of the object stored on the server. So, Resource.partial_update({id: 1, something: "updated"}) would just updated the value of the something field in the database.Elwandaelwee
But you aren't actually doing $patch on the original object? You're using partial_update to get the changed fields, then create a new object with just those fields, then doing $patch()?Sorbose
B
2

I would suggest using Restangular over ngResource. The angular team keeps improving ngResource with every version, but Restangular still does a lot more, including allowing actions like PATCH that ngResource doesn't. Here'a a great SO question comparing the two What is the advantage of using Restangular over ngResource?

Bates answered 8/10, 2013 at 22:11 Comment(1)
We looked at Restangular, but it is not clear how much of a switch it is from $resource. We have already built up a lot of code around our $resource based services so it might be more trouble than it is worth to switch to a different resource framework. Hard to tell of course before you do it.Robenarobenia

© 2022 - 2024 — McMap. All rights reserved.