Backbone.js : repopulate or recreate the view? Backbone.js : repopulate or recreate the view? javascript javascript

Backbone.js : repopulate or recreate the view?


I always destroy and create views because as my single page app gets bigger and bigger, keeping unused live views in memory just so that I can re-use them would become difficult to maintain.

Here's a simplified version of a technique that I use to clean-up my Views to avoid memory leaks.

I first create a BaseView that all of my views inherit from. The basic idea is that my View will keep a reference to all of the events to which it's subscribed to, so that when it's time to dispose the View, all of those bindings will automatically be unbound. Here's an example implementation of my BaseView:

var BaseView = function (options) {    this.bindings = [];    Backbone.View.apply(this, [options]);};_.extend(BaseView.prototype, Backbone.View.prototype, {    bindTo: function (model, ev, callback) {        model.bind(ev, callback, this);        this.bindings.push({ model: model, ev: ev, callback: callback });    },    unbindFromAll: function () {        _.each(this.bindings, function (binding) {            binding.model.unbind(binding.ev, binding.callback);        });        this.bindings = [];    },    dispose: function () {        this.unbindFromAll(); // Will unbind all events this view has bound to        this.unbind();        // This will unbind all listeners to events from                               // this view. This is probably not necessary                               // because this view will be garbage collected.        this.remove(); // Uses the default Backbone.View.remove() method which                       // removes this.el from the DOM and removes DOM events.    }});BaseView.extend = Backbone.View.extend;

Whenever a View needs to bind to an event on a model or collection, I would use the bindTo method. For example:

var SampleView = BaseView.extend({    initialize: function(){        this.bindTo(this.model, 'change', this.render);        this.bindTo(this.collection, 'reset', this.doSomething);    }});

Whenever I remove a view, I just call the dispose method which will clean everything up automatically:

var sampleView = new SampleView({model: some_model, collection: some_collection});sampleView.dispose();

I shared this technique with the folks who are writing the "Backbone.js on Rails" ebook and I believe this is the technique that they've adopted for the book.

Update: 2014-03-24

As of Backone 0.9.9, listenTo and stopListening were added to Events using the same bindTo and unbindFromAll techniques shown above. Also, View.remove calls stopListening automatically, so binding and unbinding is as easy as this now:

var SampleView = BaseView.extend({    initialize: function(){        this.listenTo(this.model, 'change', this.render);    }});var sampleView = new SampleView({model: some_model});sampleView.remove();


I blogged about this recently, and showed several things that I do in my apps to handle these scenarios:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/


This is a common condition. If you create a new view every time, all old views will still be bound to all of the events. One thing you can do is create a function on your view called detatch:

detatch: function() {   $(this.el).unbind();   this.model.unbind();

Then, before you create the new view, make sure to call detatch on the old view.

Of course, as you mentioned, you can always create one "detail" view and never change it. You can bind to the "change" event on the model (from the view) to re-render yourself. Add this to your initializer:

this.model.bind('change', this.render)

Doing that will cause the details pane to re-render EVERY time a change is made to the model. You can get finer granularity by watching for a single property: "change:propName".

Of course, doing this requires a common model that the item View has reference to as well as the higher level list view and the details view.

Hope this helps!