Zombies! RUN! (Managing Page Transitions In Backbone Apps)

One of the common issues or questions I see for Backbone.js goes something like this:

“Whenever I hit the same route more than once, I end up getting seeing this call being made multiple times. It seems to accumulate another call every time I hit the route. What’s going on?”

or

“I’ve noticed that my views are still handling events after I remove them from the screen. How do I stop this from happening?”

or

“How do I make sure I clean things up when moving to a new page in my app?”

At the heart of all of these questions is a problem that most backbone developers will run into at some point: zombies. That’s right – the living dead creatures and plague us and cause problems… only in this case, we’re referring to zombie objects in an application – otherwise known as memory leaks.

The Plague: Event Binding

The majority of the problems that people are referring to in these questions and issues are caused by the events that we bind to in our apps. Given that Backbone is an event-driven framework, it stands to reason that we’re going to be using a lot of events; and events are everywhere in Backbone apps.

We bind events to our DOM elements using the declarative `events` configuration in our views:

MyView = Backbone.View.extend({

  events: {
    "click #someButton": "doThat",
    "change #someInput": "changeIt"
  },

  doThat: function(){ ... },
  changeIt: function(){ ... }
});

We bind events from our models and collections so that our views can respond to changes and re-render themselves:

MyView = Backbone.View.extend({

  initialize: function(){
    this.model.bind("change", this.render, this);
  },

  render: function(){ ... }
});

We even use Backbone.Events in our own objects so that we can create application-level events and event-driven architectures for our apps.

MyView = Backbone.View.extend({

  initialize: function(){
    this.options.vent.bind("app:event", this.render, this);
  },

  render: function() { ... }
});

var vent = new _.extend({}, Backbone.Events);
new MyView({vent: vent});
vent.trigger("app:event");

Events are everywhere, and with good reason. They allow us to write modular, reactive code. We don’t have to stick to procedural loops and long methods that check state in order to figure out what to do next. Events allow us to know when state has changed and respond to that change appropriately.

Problems arise, though, when we bind objects together through these events but we don’t bother unbinding them. As long as these objects are bound together, and there is a reference in our app code to at least one of them, they won’t be cleaned up or garbage collected. The resulting memory leaks are like the zombies of the movies – hiding in dark corners, waiting to jump out and eat us for lunch.

For example – Backbone apps tend to have one or more areas of the screen that are updated with different views based on what the user is doing. It’s common to see a main area where the focused content is displayed, as well. When we the user interacts with our app, we update this area of the screen by having a new view instance render some new content for us. The result is new information being displayed for the user, as we expect.

The problem, though, is that it’s easy to write our code in a manner that doesn’t let us properly clean up our views when switching them out. For example, we could have a router with route methods that instantiate new views and simply replace the html of our main content area:

MyRouter = Backbone.Router.extend({
  routes: {
    "": "home",
    "post/:id": "showPost"
  },

  home: function(){
    var homeView = new HomeView();
    $("#mainContent").html(homeView.render().el);
  },

  showPost: function(id){
    var post = posts.get(id);
    var postView = new PostView({model: post});
    $("#mainContent").html(postView.render().el);
  }
});

This has the desired effect, visually. A user hitting either of these routes will see the content they expect to see. Unfortunately, though, any events that we’ve bound in our view may still be hanging around.

Rule #2: Double Tap

To correct this, I like to introduce an application object that manages the transitions between my views. This object is solely responsible for managing the content of a specific DOM element, displaying what needs to be displayed and cleaning up anything that no longer needs to be there.

function AppView(){

   this.showView(view) {
    if (this.currentView){
      this.currentView.close();
    }

    this.currentView = view;
    this.currentView.render();

    $("#mainContent").html(this.currentView.el);
  }

}
view raw 5-appview.js This Gist brought to you by GitHub.

With this in place, we can re-write our router to have a reference to an AppView object. Then, when a route method is fired, the router will tell the AppView instance to display the new view for us. Since the AppView knows about the previously displayed view still, it can call any clean up code that we need for that view.

MyRouter = Backbone.Router.extend({
  routes: {
    "": "home",
    "post/:id": "showPost"
  },

  initialize: function(options){
    this.appView = options.appView;
  },

  home: function(){
    var homeView = new HomeView();
    this.appView.showView(homeView);
  },

  showPost: function(id){
    var post = posts.get(id);
    var postView = new PostView({model: post});
    this.appView.showView(postView);
  }
});
view raw 6-router.js This Gist brought to you by GitHub.

Now our router doesn’t have to worry about creating zombies. We’ve effectively managed our application’s view transitions by introducing an object who’s sole purpose is that transition management.

Closing A View

In this AppView example, I’ve chosen to have the it call a `.close()` method on every view that it is removing from the screen. There isn’t a method called ‘close’ on a Backbone.View, by default. We need to add this method ourselves, as a convention for our application to follow.

We can add our close method to our views a few different ways: add the method to every view in our app, create our own base view with this method, or add it directly to the `Backbone.View.prototype`. There’s not necessarily anything wrong any of these choices, though my preference is to build a basic close method into the Backbone.View.prototype.

There’s a common bit of functionality that each of our views will need when closing, in most cases. We need to unbind the DOM element events, we need to unbind any custom events that our view raises, and we need to remove the HTML that represents this view from the DOM. These three things can be done in two lines of code inside of our `close` method:

Backbone.View.prototype.close = function(){
  this.remove();
  this.unbind();
}
view raw 7-close.js This Gist brought to you by GitHub.

The call to `this.remove()` delegates to jQuery behind the scenes, by calling `$(this.el).remove()`. The effect of this is 2-fold. We get the HTML that is currently populated inside of `this.el` removed from the DOM (and therefore, removed from the visual portion of the application), and we also get all of the DOM element events cleaned up for us. This means that all of the events we have in the `events: { … }` declaration of our view are cleaned up automatically!

Secondly, the call to `this.unbind()` will unbind any events that our view triggers directly – that is, anytime we may have called `this.trigger(…)` from within our view, in order to have our view raise an event.

The last thing we need to do, then, is unbind any model and collection events that our view is bound to. To do this, though, we can’t use a generic close method on our base view. If we tried to, we would likely end up writing a lot of extraneous code and potentially slowing our app down or causing issues by accidentally unbinding all events for the model or collection, everywhere. If we have multiple views listening to events on the same object, this would remove the ability for our app to respond to the model and collection changes correctly.

The solution, then, is to take a page from the WinForms / .NET world and have an “onClose” method that any view can implement when it needs to have custom code run, when the view is closed:

Backbone.View.prototype.close = function(){
  this.remove();
  this.unbind();
  if (this.onClose){
    this.onClose();
  }
}
view raw 8-close.js This Gist brought to you by GitHub.

Then in a view that has bound itself to a model or collection event, we can provide an implementation of onClose:

MyView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change", this.render, this);
  },
  render: function(){ ... },

  onClose: function(){
    this.model.unbind("change", this.render);
  }

});

Our view will now be correctly cleaned up when the AppView calls the close method.

More Than Just Views For Zombies

Of course there are far more potential zombies in our Backbone apps than just views. Any time we make a call to the `.bind(…)` method on any object, we need to be aware of the lifespan of the objects involved. If the objects are going to live for the span of the application’s life, then we may not need to do any clean up. If the objects are temporal, though, it’s likely that we will need some sort of cleanup in place.

We all need to be good citizens of the potential zombieland in stateful, event-driven application development. Know the rules, stay safe, and above all don’t be a hero.

About Derick Bailey

Derick Bailey is an independent consultant, software developer, writer, blogger, speaker and technology leader in central Texas (north of Austin). He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: MutedSolutions.com, BackboneTraining.net, WatchMeCode
This entry was posted in Backbone, Javascript, Model-View-Controller. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Daniel

    Just wanted to say a big Thank You for this excellent article. I’ve had this problem and “solved” it by calling jQuery’s undelegate and unbind before rendering my views, but this solution is far superior, will definately be using this. Once again, thanks, and keep up the good work!

  • http://mutedsolutions.com Derick Bailey

    thanks, Daniel! glad you found this useful :)

  • http://twitter.com/shaunoconnor Shaun O’Connor

    Great article… coming from  OOP languages and building view heavy applications this is exactly what you need when using Backbone.js. 

  • http://mutedsolutions.com Derick Bailey

    thanks! :)

    and i agree – the OO aspect of building good backbone apps is what really made me fall in love with it, having spent many years building MV* winforms app.

  • Jay Turpin

    Derek – thanks for the post. Very well written and
    informative. I have a couple questions:

    ·         
    From a design perspective, you chose to add
    close() and onClose() directly to the View prototype, but you didn’t add
    showView(view) to the Router prototype. Any specific reason for these two
    different approaches? Have you run into need to send in different types of
    AppView objects?

    ·        
    Do you need to cleanup a binding that looks like
    this? Or are the bindings on the view automatically cleaned up when the view is
    closed?:

    view =
    Backbone.View.extend({

        tagname: ‘li’,

        initialize: function
    () {

            _.bindAll(this,
    “render”);

        },

        render: function
    () {

           // render logic

    return this;

        }

     
    });

     

    Thanks again for all the great information

  • http://mutedsolutions.com Derick Bailey

    hi Jay,

    for the showView method: it’s not a routing concern. routing is an entry point into the application that uses a tokenized representation of the application state (the route itself) to determine what the app should look like and act like when the route method fires. you may or may not have to show a view when a route method fires. i’ve built a few applications that don’t – the route methods call models to set specific state and then views are rendered based on the model state changes.

    you could put the showView method directly into the router if your app is small and you don’t need to worry about mixing those concerns too much. but i wouldn’t put it on the router’s prototype since it won’t be needed on _every_ instance of a router that you have. 

    (fwiw: having built a handful of apps with a router, controller and appview separated out, it’s become my default implementation because i know how to scale it out as the app grows)

    for the _.bindAll bindings: this binding lives and dies with the view instance so there’s no need to worry about it. even if you decided to pass a different object as the first parameter – for example: _.bindAll(someModel, “someMethod”, “anotherMethod”) – the binding would still die with the view instance because it’s the view that references the model. 

    hope that helps!

  • Jay Turpin

    Thanks! That helps clear a couple things up for me

  • http://twitter.com/ojohnnyo Johnny Oshika

    Thanks Derick for the great information you’ve been sharing about Backbone.  I’ve been using a slightly different technique for cleaning up the model/collection event bindings that my views have subscribed to and it’s been working well for me so I thought I’d share.  I’ve also shared this with the folks who are writing the Backbone JS on Rails ebook and I believe it’s made its way into the book:

    Inside a base View (or in your case the View prototype), I keep track of all the events that the view has bound to.  I also have a method that unbinds everything automatically:

    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 = [];
    },

    In your cleanup “close” method, I would add this:

    this.unbindFromAll();

    Whenever a view needs to bind to an event, I do something like this:

    this.bindTo(this.model, ‘change’, this.someCallback);

    The benefit of this technique is that all of the events that my view has bound to get unbound automatically whenever the view is “closed”.  This way, I wouldn’t have to have an onClose method on all of my views to undo the bindings.

    Keep up the great work!

  • http://mutedsolutions.com Derick Bailey

    that’s brilliant! i’m going to have to try this out on my next project :)

  • http://twitter.com/bemusedjohnny Johnny Green

    Hey Derick, really appreciate this post.  I took this and went a step further with non-destructive view management baked in.  Multiple container management.  Let me know if i’m doing this wrong!

    https://gist.github.com/a5e924419628e54d91cc/697d6dfe165d7b3926f84a5dde7d50aa48a54a3a

  • http://twitter.com/bemusedjohnny Johnny Green

    Oops… here is a public gist.  https://gist.github.com/1259480

  • http://twitter.com/davehoff Dave Hoff

    Just have to tell you how much I appreciate your awesome posts regarding Backbone best practices. Brilliant stuff!

  • Patrick

     Great post! I didn’t realize your events are still bound after the .remove() call, glad to know this.  Time for some code cleanup tonight :)

  • http://twitter.com/ataraxia_status Nick Pannuto

    Your `backticks` aren’t working

  • http://mutedsolutions.com Derick Bailey

    yeah – i know… i keep hoping i’ll find a WordPress plugin that translates backticks into inline code blocks, like Github wiki does. one of these days, and then magically all my posts will be extra awesome! :P

  • http://twitter.com/ataraxia_status Nick Pannuto

    Dude why would you use a wordpress plugin for that? You could do that with a tiny piece of javascript. 

  • Andy

    Great article.

    How would you handle removing multiple views in the following common pattern, is this handled by backbone when the collection is reset or would you need to save all the old views in an array and loop over closing each of them?
    // current
    var listView = Backbone.View.extend({
      initialize: function(){
        this.collection.bind(‘reset’, this.addAll)
      },
      addAll: function(){
        $(this.el).empty();

        this.collection.each(this.addOne);

      },
      addOne: function(item){
        var view = new itemView({model:channel, app:this.options.app})

        $(this.el).append(view.render().el);
      }});

    // alternate
    var listView = Backbone.View.extend({
     views : [],
      addAll: function(){
        this.closeAll()
      },
      addOne: function(item){
        this.views.push(view);
      },
      closeAll: function(){
        $.each(this.views, function(view){
          view.close();
        })
      }
    })

  • http://mutedsolutions.com Derick Bailey

    hold them in an array and loop through them is the easiest way i’ve seen for handling that. i built that in to my Marionette plugin, for example. any time you have a CollectionView instance, it stores the child views in an array. closing the CollectionView will close all of the children

  • Varand

    I have a question,:

    where should I put Backbone.View.prototype.close = function(){/*code*/}
    ???? inside that zombie view?or in Backbone.js under // Backbone.View
    or inside _.extend(Backbone.View.prototype, Backbone.Events, {…}
     

  • Randy Kleinman

    5-appview.jsIs the code in “5-appview.js correct? I am getting syntax errors in firebug complaining about the syntax on this line:

    this.showView(view) {

  • http://mutedsolutions.com Derick Bailey

    … it does look a little off now that you mention it. check out the updated version of this, called RegionManager: http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/ it’s the same concept, just solidified a little more.

  • http://twitter.com/wireddaniel Daniel Murphy

    Thank you!

  • https://paydirtapp.com/ Tristan

    You saved the day, Derick. Can’t thank you enough.

  • Orangewarp

    It’s funny. Every time I run into a problem, I do a Google search and somehow, quickly find myself back here… Your articles are very clearly written, informative, and have been uber-helpful. Thanks!

  • Marco

    Thanks for a nice article! I wonder if there’s a way to double check if the old views and bindings (including sub views) are removed for sure? I was thinking about something like check if a closed view with a specific cid still exists in memory. But from what I have read, you can’t fetch a view by it’s cid? Maybe there’s a way to check which views exists in memory at a specific point?

  • http://mutedsolutions.com Derick Bailey

    There’s no way to do that at run time. You could keep a list of open views around, but that list would keep all of your views around simply by having a reference to them. They would never close. Backbone doesn’t keep a list of open views around, specifically for this reason. You can run a memory profiler in your browser (built in to chrome, safari, firebug, etc) but that’s a development tool and not something you can do in production with other people’s browsers. Your best bet is to use the development tools and memory profilers during your own testing, to make sure you’re cleaning things up… and then trust that it’s all good when it goes to production.

  • http://scottyapp.com Martin Wawrusch

    This is so politically incorrect. They are called ‘Undead Americans’ these days ;P 

    Great article, thanks for writing it.

  • Anonymous

    Hmm does this (http://documentcloud.github.com/backbone/#Events-off) change how you’d approach this topic? Backbone 0.9.x introduced some new event handling business.

  • Chris Barry

    Hi Derick,

    Thanks for this, and your other article too, really helpful. I think I may just being a bit stupid and not reading your articles properly or checking out your marionetee plugin, but would you have any views on this question?

    http://stackoverflow.com/questions/10259346/backbone-js-large-mutli-page-app-manage-page-transitions-cleanly-destruction-c 

  • Amit Garg

    Everytime I search for Backbone .. I land on your page.. then the search ends..  Thank you very much .. Keep posting..

  • http://twitter.com/vlycser Cristhian Valencia

     Excelente articulo muchas gracias.
    Yo intente realizarlo de una forma alternativa y obtuve lo siguiente:

    http://stackoverflow.com/a/10559519/1359480

  • Ansjob

    Really neat solution to this problem. Love it!

  • UBT

    Thanks for the article Derick. One naive question though, won’t removing the view take it out of DOM and what will be pattern to add it back again.

  • http://mutedsolutions.com Derick Bailey

    Yes, that’s the point.

    If you need to add it back again: http://tbranyen.com/post/missing-jquery-events-while-rendering

    But I don’t recommend doing this. I recommend creating a new view instance instead. Re-using views always leads to more trouble than its worth.

  • srigi

    Hi Derick, thanks for exelent post. Can you please share last myssing piece of code – instatiation of the router. Need to see how AppView is passed into router (I assume that that AppView function is actually constructor).

  • http://mutedsolutions.com Derick Bailey

    routers shouldn’t be used to start the application:

    http://lostechies.com/derickbailey/2012/02/06/3-stages-of-a-backbone-applications-startup/

    you should have an application object with an initialize or start method instead: 

    http://lostechies.com/derickbailey/2011/08/30/dont-limit-your-backbone-apps-to-backbone-constructs/