This post follows a blog post about some of AngularJS weaknesses, how to model being the main subject. There isn’t one idiomatic way to use a model in AngularJS and this lack of guidance was considered a problem. I think this is a strength. AngularJS doesn’t force the way you access your data, but it doesn’t leave you alone either. The different AngularJS features will give you different options that will make sense or not for your particular case. To illustrate that I’ll describe the way we make our model accessible in one of our applications, which is very different from, let’s say, the AngularJS home page examples.
Our application provides partial CRUD on pre-existing objects. It has sometimes complex business processing done server side. This is a quite common scenario in an enterprise environment. On the client side, there are many rules about what can be modified, depending on object states, privileges and ownership. So there is a lot of dependencies between different areas of our model. To tackle that issue, we use a global model. We don’t use services or resources, our model is directly on a global scope. And we don’t use the rootScope either, we use the scope corresponding to what contains our app, footer and header excluded. Here is how the AngularJS controller that defines the global scope looks like:
app.controller('GlobalCtrl', function($scope) {
$scope.model = {/* contains model objects coming from the server */};
$scope.refresher = {/* contains refresh methods allowing to refresh the model objects. Conventions says method name = object name. */};
$scope.global = {/* contains the global state only relevant for the client side. */};
});
Refreshers are method named with the same name as the objects contained in the model (user object will give user() method to refresh the user object). The refresher is the only way to update the model. The purpose of that is to have the model containing only valid committed data coming from the server.
For the controller organisation, we have more or less one controller per object, but we don’t have any hard rule there (yet). Here is a sample of one:
app.controller('UserCtrl', function($scope, $http) {
$scope.refresher.user = function() {
$http.get('app/user', {'userID': $scope.global.userID})
.success(function(data) {
$scope.model.user = data;
});
};
// initialize the data at load time
$scope.refresher.user();
});
Very simple, but what does it give us?
So if we want to modify some data, this will look like:
app.controller('UserCtrl', function($scope, $http) {
$scope.modifyName = function(newName) {
$http.post('app/user/modifyName', {'name': newName})
.success(function(data) {
$scope.refresher.user();
$scope.refresher.history();
// + potentially other refresher calls if
// we know some other parts of the model have been impacted
})
.error(function(data, status) {
displaySomeAlert(data, status);
});
};
});
We can see the use of refreshers in the code above, the relevant refreshers are called when the POST returns.
Note: From a server roundtrip point of view, this method is ineffecient. We could return the modified objects with the POST response, but that would assume that the server knows what is the data that should be updated on the screen, that’s a mix of concern I’d be ready to accept only if performance constraints made it really necessary.
My colleagues asked a few questions about that design, below are some attempts to answer.
The model is part of the scope for 2 reasons:
We could definitely use the rootScope, but it feels like the rootScope is too global for a business model. We use the rootScope, but to contain things like our resource bundles and utility methods. We might be wrong to not use the rootScope for everything global though.
Note: If you didn’t follow, only the refresher methods are allowed to update the model, so the data displayed is always committed data from the server. Also refresher must be called with the same name as the model objects.
It is a weakness, but if you show the right example and keep an eye open for dangerous behaviour, you will be fine.
Good question … There should be a way to group HTTP requests and responses together. Maybe there is a global solution that can be applied there. If you have a solution for that issue, I thank you in advance for your help!
Simplicity, expressiveness and productivity. These are also the reasons we chose AngularJS in the first place. Our app was first written (not by us) using jQuery and a cohort of plugins and libraries, and it ended up bloated, difficult to maintain … and inefficient. By migrating to AngularjJS and this global model, the amount of javascript has been reduced to 1/5 and we use much less third party libraries. The server side code was also considerably reduced. Now our main problem is that we have difficulties to get use to our new productivity as our estimates are now way too pessimistic.
Matthieu Scholler 17 June 2013