Partial Updates (aka PATCH) using a $resource based service? - angularjs

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:
Call our .diff() function to determine which fields to send, and then
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.

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)

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...)

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?

Related

Access OData JSON model mock file vs SAP backend

I have an application that works for test purposes with local JSON mock data. The oData Object contains arrays with values and the application works as desired.
Now we are switching from the local mock data File to data consumption with an oData service from a SAP backend system.
Here I get the data in JSON Objects and not all the functionality works as desired (example filter functions).
Can somebody share some thoughts about JSON Objects and Arrays with me?
And how can I get the data from the backend system in an array instead of an object?
In the mock data version I do this to define my model:
this._oModel = new JSONModel(jQuery.sap.getModulePath("myApplication", "/localService/mockdata/nodesSet.json"));
In the oData Version the model is defined in manifest.json:
this._oModel = this.getOwnerComponent().getModel();
Note: I am aware of the different names of the entities (example: nodes vs nodesSet) and this is not part of the problem.
Thanks!
One simple way would be not to create the model in the Manifest and do an explicit read in the controller as:
var oModel = new sap.ui.model.odata.v2.ODataModel(url, true);
that=this;
oModel.read( "/Products", {
urlParameters: {
"$skip": 0,
"$top": 50
},
success: function( oData) {
//here oData shall have an array of objects "results"
**-------------Set the model here using this array -> results ------**
},
error: function(oError) {
alert("error");
}
});
I am however not proud of this solution and will check if I can comment better.

filter mongoose documents based on the specific fields and attributes

I'm developing a website using the MEAN stack (MongoDB/Express/Angular/Node).
I have a product schema with 12 different fields/properties, including size, color, brand, model, etc. What is the best and most efficient way to filter products, in Angular or on the server-side?And how can i chain the results if the client had selected more than one property?What would that look like?
Assuming there will be a lot of products, it will be too much to download to the client in order to filter using Angular. It doesn't scale very well. As the list of products gets bigger and bigger, it will be less and less performant. The better way would, generally, be to let MongoDB do the filtering for you. It's very fast.
But, you can control the filtering from Angular by posting to the server the filtering term you want on the endpoint used for that method of filtering, for example, using the http module
http.post('/api/filter/' + methodOfFiltering, { 'term': termtoFilterBy }, function(dataReturned) {
// use dataReturned to do something with the data
});
Put this in an angular service method, so you can inject it into any of your controllers/components.
Create an endpoint that will use the method and the keyword in the mongoose query. I'm assuming that you're using Express for your server routes.
app.post('/api/filter/:method', function(req, res) {
var method = req.params.method;
var termToFilterBy = req.body.term;
productSchema.find({method: termToFilterBy}, function(err, products) {
res.send(products);
});
});
Let me know if this helps.

Filter cached REST-Data vs multiple REST-calls

I'm building an Angular Shop-Frontend which consumes a REST-API with Restangular.
To get the articles from the API, I use Restangular.all("articles") and I setup Restangular to cache this request.
When I want to get one article from the API, for example on the article-detail page by it's linkname and later somewhere else (on the cart-summary) by it's id, I would need 3 REST-calls:
/api/articles
/api/articles?linkname=some_article
/api/articles/5
But actually, the data from the two later calls is already available from the cached first call.
So instead I thought about using the cached articles and filter them to save the additional REST-calls.
I built these functions into my ArticleService and it works as expected:
function getOne(articleId) {
var article = $q.defer();
restangular.all("articles").getList().then(function(articles) {
var filtered = $filter('filter')(wines, {id: articleId}, true);
article.resolve((filtered.length == 1) ? filtered[0] : null);
});
return article.promise;
}
function getOneByLinkname(linkname) {
var article = $q.defer();
restangular.all("articles").getList().then(function(articles) {
var filtered = $filter('filter')(articles, {linkname: linkname}, true);
article.resolve((filtered.length == 1) ? filtered[0] : null);
});
return article.promise;
}
My questions concerning this approach:
Are there any downsides I don't see right now? What would be the correct way to go? Is my approach legitimate, to have as little REST-calls as possible?
Thanks for your help.
Are there any downsides I don't see right now?
Depends on how the functionality of your application. If it requires real time data, then having REST calls performed to obtain the latest data would be a requirement.
What would be the correct way to go? Is my approach legitimate, to have as little REST-calls as possible?
Depends still. If you want, you can explore push data notifications, such that when your data from the server is changed or modified, you could push those info to your client. That way, the REST operations happens based on conditions you would have defined.

Intercept $http request and return hardcoded data

I would like to intercept all $http calls made by various services and return an object which is declared inside the interceptor a.k.a. hardcoded data.
The request interceptor provided by Angular seems to only be able to change and return the HTTP config object.
How can I manipulate the data returned without actually calling a server?
Thanks.
To use $httpBackend from MockE2E to provide mock API responses, either for testing purposes, or to provide a mock API to develop the client against if the server API is not available, firstly you need to include angular-mocks.js as this contains ngMockE2E.
Then you need to add a module something like:
angular.module('mockBackend', [ 'ngMockE2E'])
.run(function($httpBackend) {
phones = [{name: 'phone1'}, {name: 'phone2'}];
// returns the current list of phones
$httpBackend.whenGET('/phones').respond(phones);
// adds a new phone to the phones array
$httpBackend.whenPOST('/phones').respond(function(method, url, data) {
var phone = angular.fromJson(data);
phones.push(phone);
return [200, phone, {}];
});
$httpBackend.whenGET(/^\/templates\//).passThrough();
//...
});
The above is taken from the docs at https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend - you would need to set up in here all your API endpoints that you want to return mock data for, along with any that you want real data for, eg. in the above any requests for 'templates' uses .passThrough to still forward these requests to the server, but returns mock data for API calls to '/phones'.
As far as how to turn this off and on goes it will depend on how you have your angular build process set up. If you do not want any of this in your production version you could put the above in a separate mockbackend.js file and add
angular.module('myApp').requires.push('mockBackend');
to the bottom of the file - where 'myApp' would be your app module. For your production files you can then have your build process (manual or automated) remove the angular-mocks.js and mockbackend.js requires from your index.html file and all your api calls will revert to calling the server.
If you wanted to vary if the mock backend is used or not during development you could pass a constant into the mockBackend module and use this to decided if real or mock data is returned, eg. if you have a constant 'DEV' which is a simple boolean:
if (DEV === true) {
$httpBackend.whenGET('/phones').respond(phones);
} else {
$httpBackend.whenGET(/phones).passThrough();
}

How can I use angular-cache in conjunction with the AngularJS LocalStorageModule

I have an application that when it starts gets a list of admin users.
The data looks something like this:
var users =
[
{"id":"527ddbd5-14d3-4fb9-a7ae-374e66f635d4","name":"x"},
{"id":"966171f8-4ea1-4008-b6ac-d70c4eee797b","name":"y"},
{"id":"de4e89fe-e1de-4751-9605-d6e1ec698f49","name":"z"}
]
I have a call that gets this data:
os.getUserProfiles($scope);
and the function:
getUserProfiles: function ($scope) {
$http.get('/api/UserProfile/GetSelect')
.success(function (data, status, headers, config) {
$scope.option.userProfiles = data;
});
},
I would like to avoid the admin users having to continuously issue the requests to get
the user list. I was looking at the $cacheFactory in Angular but this does not really
seem to meet my needs.
The angular-cache that's on github looks interesting but I'm not quite sure how to use
it with objects like this and then have the data stored using the LocalStorageModule.
Can someone give me an example of how they have used this product with the LocalStorageModule.
I would suggest check the extended angular-cache.
As described in documentation: http://jmdobry.github.io/angular-cache/guide.html#using-angular-cache-with-localStorage
app.service('myService', function ($angularCacheFactory) {
// This cache will sync itself with localStorage if it exists, otherwise it won't. Every time the
// browser loads this app, this cache will attempt to initialize itself with any data it had
// already saved to localStorage (or sessionStorage if you used that).
var myAwesomeCache = $angularCacheFactory('myAwesomeCache', {
maxAge: 900000, // Items added to this cache expire after 15 minutes.
cacheFlushInterval: 3600000, // This cache will clear itself every hour.
deleteOnExpire: 'aggressive', // Items will be deleted from this cache right when they expire.
storageMode: 'localStorage' // This cache will sync itself with `localStorage`.
});
});
Or we can inject custom Local storage implementation
storageImpl: localStoragePolyfill // angular-cache will use this polyfill instead of looking for localStorage
I am using this plugin https://github.com/grevory/angular-local-storage, which is really simple to use, while providing full API.
Also check a local storage usage for caching the templates: how to cache angularjs partials?
NOTE: This article Power up Angular's $http service with caching, and mostly the section:
Advanced caching, was for me the reason to move to angular-cache
How to create a custom Polyfill? (Adapter pattern)
As documented here, we need to pass the localStoragePolyfill implementing this interface:
interface Storage {
readonly attribute unsigned long length;
DOMString? key(unsigned long index);
getter DOMString getItem(DOMString key);
setter creator void setItem(DOMString key, DOMString value);
deleter void removeItem(DOMString key);
void clear();
};
angular-cache cares only about these three methods:
setItem
getItem
removeItem
I.e.: Implement a wrapper talking with local-storage API, transforming it to the advanced cache api. That's it. Nothing else

Resources