Building Models in Backbone.js and AngularJS
Join the DZone community and get the full member experience.
Join For FreeThis is the second article in the series contrasting how Backbone and Angular help with the things we have to deal with day to day when building web applications. This time we will look into how we build models and implement business logic in both frameworks.
Series
Backbone
Backbone, similar to many client-side frameworks, is built around observable properties. Therefore, in order for the view to reflect the changes of a particular model, that model has to emit events. In most applications it is not just the view that listens to those events, but other models as well.
Since this need of keeping multiple components in sync is so common, a large chunk of the business logic of the application goes into Backbone models and collections. Quite often these objects also correspond to resources on the backend.
Let’s look at an example of the Account model implemented in Backbone:
var Transaction = Backbone.Model.extend({ amount: function(){ return this.get('amount'); }, isDebit: function(){ return this.amount() > 0; }, isCredit: function(){ return this.amount() < 0; } //... }); var Transactions = Backbone.Collection.extend({ model: Transaction //... }); var Account = Backbone.Model.extend({ //... balance: function(){ return this.get('balance'); }, transactions: function(){ return this.get('transactions'); }, addTransaction: function(transaction){ this.transactions().add(transaction); } });
This is how it can be used:
var account = new Account({ number: '87654321', transactions: new Transactions([ {id: 1, amount: 10} ]) }); account.addTransaction(new Transaction({id: 2, amount: -5}));
Having to extend Backbone.Model
and Backbone.Collection
adds quite a bit of complexity.
POJOs and Models
First, it separates all domain objects into POJOs (or JSON data) and Backbone models. POJOs are used when rendering templates and talking to the server. Backbone models are used when observable properties are needed (e.g., setting up data bindings).
This often leads to having two versions of the same object: one for data bindings, and the other one for rendering. Calling toJSON
is a standard way to convert one into the other.
var Account = Backbone.Model.extend({ // View uses toJSON to render the template toJSON: function(){ return _.merge(this.attributes, { transactions: this.transactions().toJSON() }) } });
Computed Properties
Second, extending Backbone.Model
and Backbone.Collection
promotes mutability. Since Backbone does not support observing functions, every computed property has to be reset when any of the source properties changes. This adds a lot of accidental complexity, which results in code that is hard to understand and test. On top of that, all the dependencies have to be explicitly specified.
The following is an example of a computed property:
var Account = Backbone.Model.extend({ //... initialize: function(){ this.transactions().on("add remove", this._recalculateBalance, this); this._recalculateBalance(); }, balance: function(){ return this.get('balance'); }, transactions: function(){ return this.get('transactions'); }, _recalculateBalance: function(){ var newBalance = this.transactions().reduce(function(sum, transaction){ return sum + transaction.amount(); }, 0); this.set('balance', newBalance); } });
Using Add-ons
Even though using add-ons can mitigate this issue, they do not solve the main problem: computed properties are mutable and have to be proactively recalculated.
Angular
Since Angular does not use observable properties, it does not restrict you when it comes to implementing the model. There is no class to extend and no interface to comply. You are free to use whatever you want (including existing Backbone models). In practice, most developers use plain old JavaScript objects.
Let’s look at an example of how we would implement the Account model in Angular.
function Transaction(attrs) { _.extend(this, attrs); } _.extend(Transaction.prototype, { isDebit: function(){ return this.amount > 0; }, isCredit: function(){ return this.amount < 0; } }); function Account(attrs){ _.extend(this, attrs); } _.extend(Account.prototype, { addTransaction: function(transaction){ this.transactions.push(transaction); } });
We can use it as follows:
var account = new Account({ number: '87654321', transactions: [ new Transaction({id: 1, amount: 10}) ] }); account.addTransaction(new Transaction({id: 2, amount: -5}));
Just POJOs
- Same objects are used to render views and implement business logic, so there is no need to implement
toJSON
. - They are framework-agnostic, which makes reusing them across applications easier.
- They are close to the data that is being sent over the wire, which simplifies the client-server communication.
Computed Properties
Computed properties can be modeled as functions:
function Account(attrs){ _.extend(this, attrs); } _.extend(Account.prototype, { //... balance: function(){ return this.transactions.reduce(function(sum, transaction){ return sum + transaction.amount; }, 0); } });
They are more testable, and, in general, easier to reason about.
Going OO or FP
Angular does not impose any constraints on how your models are built. The Account
model in the example is implemented in the traditional object-oriented style, so we can compare it with the Backbone implementation. You, however, are free to use any style you want.
- A big fan of Domain Driven Deisgn? Like building rich domain models comprising entities and values? Not a problem.
- Like functional programming, persistent data structures, and data transformations? Use those.
Doing either in Backbone is pretty much impossible.
Summing Up
The two frameworks have very different approaches when it comes to building models.
- Since Backbone is built around observable propeties, you are forced to extend
Backbone.Model
andBackbone.Collection
. - Angular, on the other hand, does not prescribe anything in this area. You can use DDD, FP, or whatever works for you.
Depending on the complexity of the model it can have a profound effect on the maintainability of your application.
In the next article I will cover constructing the DOM and implementing view logic.
Published at DZone with permission of Victor Savkin, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Transactional Outbox Patterns Step by Step With Spring and Kotlin
-
From On-Prem to SaaS
-
Essential Architecture Framework: In the World of Overengineering, Being Essential Is the Answer
-
The SPACE Framework for Developer Productivity
Comments