Skip to content Skip to sidebar Skip to footer

Backbone.js: How To Unbind From Events, On Model Remove

in backbone we have an app that uses an event Aggregator, located on the window.App.Events now, in many views, we bind to that aggregator, and i manually wrote a destroy function o

Solution 1:

I see that even when the binding event direction is this way Object1 -> listening -> Object2 it has to be removed in order to Object1 lost any alive reference.

And seeing that listening to the Model remove event is not a solution due it is not called in a Collection.reset() call then we have two solutions:

1. Overwrite normal Collection cleanUp

As @dira sais here you can overwrite Collection._removeReference to make a more proper cleaning of the method.

I don't like this solutions for two reasons:

  • I don't like to overwrite a method that has to call super after it.
  • I don't like to overwrite private methods

2. Over-wrapping your Collection.reset() calls

Wich is the opposite: instead of adding deeper functionality, add upper functionality.

Then instead of calling Collection.reset() directly you can call an implementation that cleanUp the models before been silently removed:

cleanUp: function( data ){
  this.each( function( model ) { model.unlink(); } );
  this.reset( data );
} 

A sorter version of your code can looks like this:

AppEvents = {};
_.extend(AppEvents, Backbone.Events)

varUser = Backbone.Model.extend({
  initialize: function(){
    AppEvents.on('my_event', this.listen, this);
  },

  listen: function(){
    console.log("%s still listening...", this.get('name'));
  },

  unlink: function(){
   AppEvents.off( null, null, this );
  }
});

varUsers = Backbone.Collection.extend({
  model: User,

  cleanUp: function( data ){
    this.each( function( model ) { model.unlink(); } );
    this.reset( data );
  }
});


// testingvar users = newUsers([{name: 'John'}]);
console.log('users.size: ', users.size()); // 1AppEvents.trigger('my_event');             // John still listening...

users.cleanUp();
console.log('users.size: ', users.size()); // 0AppEvents.trigger('my_event');             // (nothing)

Check the jsFiddle.

Update: Verification that the Model is removed after remove the binding-event link

First thing we verify that Object1 listening to an event in Object2 creates a link in the direction Obect2 -> Object1:

Our object is retained

In the above image we see as the Model (@314019) is not only retained by the users collection but also for the AppEvents object which is observing. Looks like the event linking for a programmer perspective is Object that listen -> to -> Object that is listened but in fact is completely the opposite: Object that is listened -> to -> Object that is listening.

Now if we use the Collection.reset() to empty the Collection we see as the users link has been removed but the AppEvents link remains:

Our object is retained 2

The users link has disappear and also the link OurModel.collection what I think is part of the Collection._removeReference() job.

When we use our Collection.cleanUp() method the object disappear from the memory, I can't make the Chrome.profile tool to explicitly telling me the object @314019 has been removed but I can see that it is not anymore among the memory objects.

Solution 2:

I think the clean references process is a tricky part of Backbone.

When you remove a Model from a Collection the Collection takes care to unbind any event on the Model that the Collection its self is binding. Check this private Collection method.

Maybe you can use such a same technique in your Aggregator:

// ... Aggregator code
the_model.on( "remove", this.unlinkModel, this );
// ... more Aggregator code

unlinkModel: function( model ){
  model.off( null, null, this );
}

This is in the case the direction of the binding is Aggregator -> Model. If the direction is the opposite I don't think you have to make any cleaning after Model removed.

Solution 3:

Instead of wrapping Collection's reset with cleanUp as fguillen suggested, I prefer extending Collection and overriding reset directly. The reason is that cleanUp takes effect only in client's code, but not in library(i.e. Backbone)'s. For example, Collection.fetch may internally call Collection.reset. Unless modifying the Backbone's source code, we cannot unbind models from events(as in cleanUp) after calling Collection.fetch.

Basically, my suggested snippet is as follows:

varMyCollection = Backbone.Collection.extend({
        reset: function(models, options) {
            this.each(function(model) {
                model.unlink(); // same as fguillen's code
            });
            Backbone.Collection.prototype.reset.apply(this, arguments);
        }
    });

Later, we can create new collections based on MyCollection.

Post a Comment for "Backbone.js: How To Unbind From Events, On Model Remove"