I'm building an app, that is backed with node-mysql combo, and angularjs on the frontend part. The backend REST service is ready, but I'm struggling with modeling my relational data. There are some questions regarding this like : $resource relations in Angular.js or $resource relations in Angular.js [updated] . Are those approaches still the best approaches, or were there any significant changes in $resource ? Or maybe Restangular is the way to go?
3 Answers
Here is my technique:
I declare a factory called dataService
, which is a wrapper around Restangular
, extended with some other features.
First let me gave some code and then explain:
.factory('identityMap',
var identityMap = {};
return {
insert: function(className, object) {
if (object) {
var mappedObject;
if (identityMap[className]) {
mappedObject = identityMap[className][object.id];
if (mappedObject) {
extend(mappedObject, object);
} else {
identityMap[className][object.id] = object;
mappedObject = object;
}
} else {
identityMap[className] = {};
identityMap[className][object.id] = object;
mappedObject = object;
}
return mappedObject;
}
},
remove: function(className, object) {
if (identityMap[className] && identityMap[className][id]) delete identityMap[className][id];
},
get: function(className, id) {
return identityMap[className] && identityMap[className][id] ? identityMap[className][id] : null;
},
flush: function(){
identityMap = {};
}
};
}
.factory('modelService', ['Restangular', 'identityMap', '$rootScope', '$log', function(Restangular, identityMap, $rootScope, $log) {
var ENUM1 = {STATE:0, OTHER_STATE:1, OTHER_STATE2: 2},
ENUM2 = {OK:0, ERROR:1, UNKNOWN:2};
function extendModel(obj, modelExtension, modelName){
angular.extend(obj, modelExtension);
obj.initExtension();
obj = identityMap.insert(modelName, obj);
}
function broadcastRestEvent(resourceName, operation, data){
$rootScope.$broadcast(resourceName + $filter('capitalize')(operation), data);
}
var resource1Extension = {
_extensionFunction1: function() {
// ... do something internally ...
if (this.something){
// this.newValue ....
;
}
else {
// ....;
}
},
publicExtensionFunction: function(param1) {
// return something
},
initExtension: function() {
this._extensionFunction2();
extendModel(this.resource2, resource2Extension, 'resource2');
}
};
var resorce2Extension = {
_extensionFunction1: function() {
// do something internally
},
publicExtensionFunction = function(param1) {
// return something
},
initExtension: function(){
this._extensionFunction1;
}
};
var modelExtensions = {
'resource1': resource1Extension,
'resource2': resorce2Extension
};
var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('/api');
RestangularConfigurer.setOnElemRestangularized(function(obj, isCollection, what, Restangular){
if (!isCollection) {
if (modelExtensions.hasOwnProperty(what)) {
extendModel(obj, modelExtensions[what], what);
}
else {
identityMap.insert(what, obj);
}
if (obj.metadata && obj.metadata.operation) {
broadcastRestEvent(what, obj.metadata.operation, obj);
}
}
return obj;
});
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newData;
if (operation === 'getList') {
newData = data.objects;
newData.metadata = {
numResults: data.num_results,
page: data.page,
totalPages: data.total_pages,
operation: operation
};
data = newData;
}
else if (operation === 'remove') {
var splittedUrl = url.split('/');
var id = splittedUrl.pop();
var resource = splittedUrl.pop();
identityMap.remove(resource, id);
broadcastRestEvent(resource, operation, id);
}
else {
data.metadata = {operation: operation};
}
return data;
});
});
return {
rest: rest,
enums: {
ENUM1: ENUM1,
ENUM2: ENUM2
},
flush: identityMap.flush,
get: identityMap.get
}
}]);
1) Let me explain identityMap
(it's the code from this blog post with some extended features):
- Let's consider a REST model which looks like this (each resource represents a database table):
resource 1:
id = Integer
field1 = String
field2 = String
resource2s = [] (List of resources2 which points to this resource with their foreign key)
resource 2:
id = Integer
field1 = String
field2 = String
...
resource1_idfk = Foreign Key to resource 1
Resource API is so smart that it returns
resource1
relationships withresources2
withGET /api/resource1/1
to save the overhead that you would get withGET
toresource2
with some query parameters toresource1_idfk
...The problem is that if your app is doing the
GET
toresource1
and then somewhere laterGET
toresource2
and edits theresource2
, the object representing theresource2
which is nested inresource1
would not know about the change (because it is not the same Javascript object reference)The identity map solves this issue, so you hold only one reference to each resource's instance
So, for example, when you are doing an update in your controller the values automatically updates in the other object where this resource is nested
The drawback is that you have to do memory management yourself and flush the identity map content when you no longer need it. I personally use Angular Router UI, and define this in a controller which is the root of other nested states:
$scope.$on("$destroy", function() { modelService.flush(); });
The other approach I use within the Angular Router UI is that I give the id of the resource which i want to edit/delete within that controller as the parameter of nested state and within the nested state i use:
$scope.resource1instance = modelService.get('resource1', $stateParams.id);
You can than use
resource1.put(...).then(
function(){
// you don't need to edit resource1 in list of resources1
$state.go('^');
}
function(error){
handleError(error);
});
2) When I need to use some new functionality over resources I use `Restangular's setOnElemRestangularized. I think the code above is self explanatory and very similar to the one mentioned in blog post I have mentioned above. My approach is slightly different from the one in that post, that I don't use the mixin initialization before, but after I mix it to the object, so one could reference the new functions in initializer. The other thing I don't use, for example, he creates single factory for every resource, for example Proposal for extended logic and the other factory ProposalSvc for manipulating the instances. For me that's a lot of code you don't have to write and personally I think that Javascript is not suited very well for this object oriented approach, so I return just the whole Restangular object and do operations with it.
3) Another thing I have there is the broadcast of events when something in my model changes with Restangular, this is something I needed when I used ng-table. For example, when the model changed and rows in my table needed to be updated to reference the changes, so in the controller which manages the table I use $scope.on('eventName')
and then change appropriate row. These events are also great when you have a multiuser live application and you use websockets for server notifications (code not included here in modelService). For example somebody deletes something in a database, so the server sends a notification to everyone who is alive through websocket about the change, you then broadcast the same event as used in Restangular and the controller does the same edits on its data.

- 7,913
- 10
- 38
- 43

- 109
- 2
This blog post should help you make your choice http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
I agree that there are a lot of good practices using http headers in Restangular, but you can pick them in the source and use them directly.
What you have to wonder is, will you be able to wrap your nested resources within a $resource and make instance calls while modifying the parent object. And that's not a given.

- 673
- 4
- 14
-
Things covered in Sauce blogpost won't help here I guess :) – mike_hornbeck Jul 31 '14 at 15:07
-
Sure they will, look at the comments too. There is no definitive answer to your question and there is no best approach. It heavily depends on how you plan to design your api and on the business model. – Gepsens Jul 31 '14 at 15:45
Your question seems to be asking whether you should be using ngResource, Restangular or some other framework or drop down to the low-level and use $http directly.
$resource is still widely used because it's included in the official docs and in all the popular tutorials and articles but Restangular is fairly popular.
The website ngModules shows a listing of REST API modules for AngularJS.
If you have a simple REST API, go with $resource for now and then switch to Restangular if you're doing lots of custom coding and filtering. It is a much nicer framework and more extensible.