Using Arrays and Objects in Backbone.js Models

If you've ever attempted to use arrays or objects on a Backbone.js model, you might have run into some strange behavior.

At first glance, everything seams perfectly normal. You can define a model with arrays/objects as properties no problem.

var Dog = Backbone.Model.extend({
  defaults: {
    legs: 4,
    likes: ["eating", "belly rubbs", "barking at people"]
  }
});

var skip = new Dog({ name: "Skip" });
skip.get('likes');
// -> ["eating", "belly rubbs", "barking at people"]

But as soon as you try to change those properties you'll see that your model doesn't fire change events consistently.

Setting the property to a new array does fire the change event.

skip.set('likes', ["playing fetch"]);
// -> 'changed!'

skip.get('likes');
// -> ["playing fetch"]

Using .get() and changing the array does not fire the change event, but does change the model.

skip.get('likes').push("eating shoes");
// ->

skip.get('likes');
// -> ["playing fetch", "eating shoes"]

(now it gets really weird)

Using .get(), changing the array, and re-setting it also does not fire the change event, but does change the model.

var newlikes = skip.get('likes');
newlikes.push("walks");
// our model is still the same

skip.set('likes', newlikes);
// ->

skip.get('likes');
// -> ["playing fetch", "eating shoes", "walks"]

So what to do now?

Always use _.clone()

Underscore (a dependency of backbone), includes a handy clone() function that can fix this problem. If you clone the array first, it will behave just like any other property.

var newlikes2 = _.clone(skip.get('likes'));
newlikes2.pop();

skip.set('likes', newlikes2);
// -> 'changed!'

skip.get('likes');
// -> ["playing fetch", "eating shoes"]

This problem can be especially tricky to catch, because you most often won't notice it until your views are only rendering part of the time.

If you want to read more on this problem, there's a good in-depth answer on stackoverflow on the subject.