1
votes

Backbone does not trigger 'change' event on model, when we set an updated object. Look at this code

var MyModel = Backbone.Model.extend({
  defaults: {
    x: {}
  }
});

var myModel = new MyModel();
myModel.on('change', function() { console.log('Changed'); });

var x = myModel.get('x');
x.something = 5; // Update the object
myModel.set('x', x); // Set the object. Does not trigger change event

Solution:

Backbone Model has stored a reference 'x' to the object {}. When we update the object and set the object again into the model, reference is still the same So it does not trigger change event.

One of the approaches is Clone the object before setting into the model. Look at this code

var MyModel = Backbone.Model.extend({
  defaults: {
    x: {}
  }
});

var myModel = new MyModel();
myModel.on('change', function() { console.log('Changed'); });

var x2 = _.clone(myModel.get('x'));
x2.something = 6;
myModel.set('x', x2);

Q: Is there a better approach for this problem ? I am not sure if I have done it right way.

1

1 Answers

1
votes

This is tricky. By default you cannot do that, for the reasons you already mentioned. The object you get out is the same object you set later. So not only does it not detect the change event, you also change the data inside the model without realizing it when you do x.something = 5;.

And it's even worse than that:

var MyModel = Backbone.Model.extend({
  defaults: {
    x: {}
  }
});

var myModel = new MyModel();
var myModel2 = new MyModel();

var x = myModel.get('x');
x.something = 5;

console.log(myModel2.get('x'));
>> { something: 5 }

The default for x will be shared across all instances because it is part of the prototype, gets copied to the instance and still references the same object of the prototype. So basically, you cannot use defaults for anything other than booleans, numbers and strings, and you cannot get any change events on any other types either unless you clone them before mutating or pass in new unique objects.

Backbone really does not work well at all with non-flat data structures in its attributes.

Your solution is one of the possibilities, although I would probably extend the base Backbone.Model and put the _.clone call into the get method.

var DeepModel = Backbone.Model.extend({
    get: function (attr) {
        return _.clone(Backbone.Model.prototype.get.apply(this, arguments));
    }
});

var myModel = new DeepModel({
    x: {
        something: 5
    }
});

myModel.on('change', console.log.bind(console, 'Changed'));
var x = myModel.get('x');
x.something = 10;
myModel.set('x', x);

Then you can also override the set method to trigger more exact events so that you can also see what field changed exactly. change:x would be a little bit useless to know.

Of course other people have already had the same problems. I know of at least two libraries that attempt to solve this kind of problem:

Backbone Relational
and backbone-deep-model

Backbone Relational is pretty heavy, so it might not really be the right choice to just deal with non-flat attributes on a model. I've never used backbone-deep-model. Just check it out and see if it fits your use case. I bet there are also other libraries that solve this issue. Or just come up with your own way of solving this problem and open source the solution.