HTML5 Zone is brought to you in partnership with:

Victor is a ruby developer at Nulogy. He has worked a lot with Java and Ruby platforms. Being a big fan of domain specific languages he likes to blog about implementing them using Groovy, Ruby or Clojure. Victor is a DZone MVB and is not an employee of DZone and has posted 44 posts at DZone. You can read more from them at their website. View Full User Profile

Building Models in Backbone.js and AngularJS

11.26.2013
| 11619 views |
  • submit to reddit

This 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 toJSONis 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.Modeland Backbone.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, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)