I am experimenting few best practices in AngularJS specially on designing model. One true power in my opinion in AngularJS is
'When model changes view gets updated & vice versa'
. That leads to the obvious fact
'At any given time the model is the single source of truth for application state'
Now, After reading various blog posts on designing the right model structure and I decided to use something like 'Single Object' approach. Meaning the whole app state is maintained in a single JavaScript object.
For example of a to-do application
$scope.appState = {
name: "toDoApp",
auth: {
userName: "John Doe",
email: "[email protected]",
token: "DFRED%$%ErDEFedfereRWE2324deE$%^$%#423",
},
toDoLists: [
{ name: "Personal List",
items: [
{ id: 1, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 0},
{ id: 2, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 1},
{ id: 3, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 0}]
},
{ name: "Work List",
items: [
{ id: 1, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : -1},
{ id: 2, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 0},
{ id: 3, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 0}]
},
{ name: "Family List",
items: [
{ id: 1, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 2},
{ id: 2, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 0},
{ id: 3, task: "Do something ", dueDate: "2013-01-10 11:56:05", status:"Open", priority : 5}]
}
]
};
This object will go HUGE depending on the application complexity. Regarding this I have the below worries and marked them as questions.
Is such approach advisable? What are the downsides and pitfalls I will face when application starts to scale?
When small portion of object is updated say priority is increased will angular smartly re-render the delta alone or will it consider the object got changed and re-render whole screen? (This will lead to poor performance), If so what are the works around?
Now since the whole DOM got smoothly translated into one JavaScript object the application has to keep manipulating this object. Do we have right tools for complex JavaScript object manipulation like jQuery was king of DOM manipulator?
With the above doubts I strongly find the below advantages.
Data has got neatly abstracted & well organized so that anytime it can be serialized to server, firebase or local export to user.
Implementing crash recovery will be easy, Think this feature as 'Hibernate' option in desktops.
Model & View totally decoupled. For example, company A can write Model to maintain state and few obvious Controllers to change the model and some basic view to interact with users. Now this company A can invite other developer to openly write their own views and requesting the company A for more controllers and REST methods. This will empower LEAN development.
What if I start versioning this object to server and I can make a playback to the user in the SAME way he saw the website and can continue to work without hassle. This will work as a true back button for single page apps.
The missing piece in AngularJS A model object is the JavaScript version of a class instance. Developers familiar with object oriented programming will feel right at home using model objects.
The model in an MVC-based application is generally responsible for modeling the data used in the view and handling user interactions such as clicking on buttons, scrolling, or causing other changes in the view. In basic examples, AngularJS uses the $scope object as the model.
AngularJS ng-model Directive The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.
The $scope in an AngularJS is a built-in object, which contains application data and methods. You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML).
In my day job we use the "state in a single object" pattern in a big enterprise AngularJS application. So far I can only see benefits. Let me address your questions:
Is such approach advisable? What are the downsides and pitfalls I will face when application starts to scale?
I see two main benefits:
1) DEBUGGING. When the application scales, the toughest question to answer is what is happening to my application right now? When I have all the application state in one object, I can just print it on the console at any point in time while the application is running.
That means it's much easier to understand what is happening when an error occurs, and you can even do it with the application on production, without the need to use debugging tools.
Write your code using mostly pure functions that process the state object (or parts of it), inject those functions and the state in the console, and you'll have the best debugging tool available. :)
2) SIMPLICITY. When you use a single object, you know very clearly on your application what changes state and what reads state. And they are completely separate pieces of code. Let me illustrate with an example:
Suppose you have a "checkout" screen, with a summary of the checkout, the freight options, and the payment options. If we implement Summary
, Freight
and PaymentOptions
with separate and internal state, that means that every time the user changes one of the components, you have to explicitly change the others.
So, if the user changes the Freight
option, you have to call the Summary
in some way, to tell it to update its values. The same have to happen if the user selects a PaymentOption
with a discount. Can you see the spaghetti code building?
When you use a centralized state object things get easier, because each component only interacts with the state object. Summary
is just a pure function of the state. When the user selects a new freight or payment option, the state is updated. Then, the Summary
is automatically updated, because the state has just changed.
When small portion of object is updated say priority is increased will angular smartly re-render the delta alone or will it consider the object got changed and re-render whole screen? (This will lead to poor performance), If so what are the works around?
We encountered some performance problems when using this architecture with angular. Angular dirty checking works very well when you use watchers on objects, and not so well when you use expensive functions. So, what we usually do when finding a performance bottleneck is saving a function result in a "cache" object set on $scope
. Every time the state changes we calculate the function again and save it to the cache. Then reference this cache on the view.
Now since the whole DOM got smoothly translated into one JavaScript object the application has to keep manipulating this object. Do we have right tools for complex JavaScript object manipulation like jQuery was king of DOM manipulator?
Yes! :) Since we have a big object as our state, each and every library written to manipulate objects can be used here.
All the benefits you mentioned are true: it makes it easier to take snapshots of the application, serialize it, implement undos...
I've written some more implementation details of the centralized state architecture in my blog.
Also look for information regarding frameworks that are based on the centralized state idea, like Om, Mercury and Morearty.
Is such approach advisable? What are the downsides and pitfalls I will face when application starts to scale?
I'd say overall this is probably not a great idea since it creates major problems with maintainability of this massive object and introduces the possibility of side effects throughout the entire application making it hard to properly test.
Essentially you get no sort of encapsulation since any method anywhere in the app might have changed any part of the data model.
When small portion of object is updated say priority is increased will angular smartly re-render the delta alone or will it consider the object got changed and re-render whole screen? (This will lead to poor performance), If so what are the works around?
When a digest occurs in angular all the watches are processed to determine what has changed, all changes will cause the handlers for the watches to be called. So long as you are conscious of how many DOM elements you're creating and do a good job of managing that number performance isn't a huge issue, there are also options like bind-once to avoid having too many watchers if that becomes an issue (Chrome profiling tools are great for figuring out if you need to work on these problems and to find the correct targets for performance)
Now since the whole DOM got smoothly translated into one JavaScript object the application has to keep manipulating this object. Do we have right tools for complex JavaScript object manipulation like jQuery was king of DOM manipulator?
You can still use jQuery but you would want to do any DOM manipulations within directives. This allows you to apply them within the view which is really the driver of the DOM. This keeps your controllers from being tied to the views and keeps everything testable. Angular includes jQLite a smaller derivative of jQuery but if you include jQuery before angular it will use that instead and you can use the full power of jQuery within Angular.
Data has got neatly abstracted & well organized so that anytime it can be serialized to server, firebase or local export to user. Implementing crash recovery will be easy, Think this feature as 'Hibernate' option in desktops.
This is true but I think it's better if you come up with a well defined save object that persists the information you need to restore state rather than storing the entire data model for all the parts of the app in one place. Changes to the model over time will cause problems with the saved versions.
Model & View totally decoupled. For example, company A can write Model to maintain state and few obvious Controllers to change the model and some basic view to interact with users. Now this company A can invite other developer to openly write their own views and requesting the company A for more controllers and REST methods. This will empower LEAN development.
So long as you write your models in your controllers this still remains true and is an advantage. You can also write custom directives to encapsulate functionality and templates so you can greatly reduce the complexity of your code.
What if I start version control this object to server and I can make a playback to the user in the SAME way he saw the website and can continue to work without hassle. This will work as a true back button for single page apps.
I think ngRoute or ui-router really already cover this for you and hooking into them to save the state is really a better route (no pun intended).
Also just my extended opinion here, but I think binding from the view to the model is one of the great things using Angular gives us. In terms of the most powerful part I think it's really in directives which allow you to extend the vocabulary of HTML and allow for massive code re-use. Dependency injection also deserves an honorable mention, but yeah all great features no matter how you rank 'em.
I don't see any advantages in this approach. How is one huge object more abstracted away than, say, application model (app name, version, etc), user model (credentials, tokens, other auth stuff), toDoList model (single object with name and collection of tasks).
Now regarding decoupling of view and model. Let's say you need a widget to display current user's name. In Single Object approach, your view would look something like this:
<div class="user" ng-controller="UserWidgetCtrl">
<span>{{appState.auth.userName}}</span>
</div>
Compare with this:
<div class="user" ng-controller="UserWidgetCtrl">
<span>{{user.userName}}</span>
</div>
Of course, you might argue, that it is up to UserWidgetController to provide access to user object, thus hidding structure of appState
. But then again controller must be coupled to appState
structure:
function UserWidgetCtrl($scope) {
$scope.user = $scope.appState.auth;
}
Compare with this:
function UserWidgetCtrl($scope, UserService) {
UserService.getUser().then(function(user) {
$scope.user = user;
})
}
In latter case, the controller does not get to decide, where user data comes from. In former case, any change in appState
hierarchy means that either controllers or view will have to be updated. You could however keep single object behind the scenes, but access to separate parts (user, in this case) should be abstracted by dedicated services.
And don't forget, as your object structure goes, your $watch
'es will get slower and consume more memory, especially with object equality flag turned on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With