I’ve read a post comparing Backbone to AngularJS recently, and another that ended with similar conclusions about Backbone, and since I’ve been working with Backbone for the last year or so, I thought it would be a good idea to share my own experiences with it.
In 2012 I’ve been working with a few other developers at Lunar Logic on a new webapp. We’ve decided from the beginning to build it as a single page application, based on an API that was being developed for an existing mobile app. We have considered EmberJS for a moment, but we’ve decided it probably wasn’t stable enough at that point, so we went with Backbone instead.
Looking back I think we made the right choice back then – EmberJS has changed a lot since then (some important parts were still being changed a couple of months ago in 1.0-pre versions), and they still haven’t released a final 1.0, though it seems it’s getting close to that. I’m also glad I had a chance to learn Backbone and get to see its pros and cons. Still, if I was starting the same project right now, I’d probably choose Ember instead.
So what have I learned about Backbone during this year?
Backbone is awesome
Seriously, if you’re coming from the pre-MVC world where all you had was jQuery, the difference is enormous. I’ve written some complex JS apps before (2009-10), and I remember how much work I had to put in them that would be done for me by Backbone now.
The apps often started as a collection of JS objects that were really namespaces for functions that handled some specific area (profiles page, map page etc.). It took a lot of thinking and refactoring to make it look like a kind of MVC, with some model and collection classes handling the data, but it was all written from scratch, mostly by trial and error.
Just having the structure that you can base your views, models and collections on, something that tells you how to arrange all this code already makes such a big difference. Having some helper code in the base classes is even better.
I wish I had Backbone back in 2009, it would have made my life so much easier.
… unless your app is complex
For the kind of apps that I wrote then, Backbone could still be enough. They didn’t have that many different views, and they had a higher Rails-to-JS ratio.
Handling view lifecycle
An important part of using Backbone views is handling their cleanup when you move between them. The version of Backbone that I’ve worked with didn’t help you here at all, and if you forgot about it, you’d run into some really hard to debug problems. I spent countless hours trying to figure out why some tests weren’t working, only to find out that a zombie view from a previous test was handling clicks because it wasn’t properly disconnected from an element.
I’ve decided to handle this in a more general way and wrote some code in my base view class that handled this for all views in the project, if they only followed the conventions and provided necessary callbacks.
Version 0.9.9 released in December adds a similar mechanism in the form of a
#listenTo method which stores the events observed by a view in its internal field and then unbinds them later automatically. I still prefer my solution though, because it allowed you to reload a whole view, re-rendering its contents and subviews and rebinding all the connections; the built-in feature only works if you remove the view with its element from the DOM and create and render a new instance in its place. (I might put this code on GitHub one day if I find some free time to extract it from the app.)
Building view layouts
Another aspect of views that Backbone doesn’t help you with is building a hierarchy of views that are nested in one another. I thought it would be a good idea to be able to reuse as much of the view as possible when you’re moving between them, i.e. to keep as much of the header/footer and navigation areas as possible on the page and rebuild only the part that actually needs to change.
It was a good idea indeed, but again, I had to write a lot of custom code to support it. I ended up with a system where every view specifies a “parent view” that wraps it on the page (feed page –> page with 2nd level navigation –> page with a header&footer –> main layout), and the view is built from inside out until a “lowest common denominator” is found with the previously open view. It worked fine in most cases, but it still wasn’t as flexible as I’d like to.
Keeping views up to date
If you render the views on the client side, you also have to update them on the client side. User types or clicks something, a value of a field is changed, a record is added or deleted, and so on, and the UI needs to be updated. Backbone helps you bind the events to actions, but you have to write those actions yourself.
We could have used one of the many model-view binding plugins for Backbone, but we put this off for later and never got around to choosing one. What we did instead is a combination of two strategies. For simpler changes, we’d just bind a change event to a method that manually modified a field using jQuery. For anything that was too complex for that, we re-rendered either a partial or the whole view.
It actually worked quite well; we could get away with something like this because the contents of the views didn’t change that often, so reloading whole views sometimes wasn’t a performance issue, and thanks to the lifecycle code you could safely reload anything at any time and not worry about the bindings.
Most of the time the models need to be somehow connected to each other, arranged in various kinds of relations. When you parse a response from the server, it often contains a whole hierarchy of nested models. Backbone calls the
#parse method in your models, but you need to implement it and build the nested objects manually.
It’s not a huge problem, but it does lead to a lot of duplicated code that’s just copy-pasted from one model to another, and it would be much better to specify the relations declaratively and have some generic code that would parse the json based on the specified rules.
I’m not a hardcore TDD fan, I don’t try to test every layer in every aspect; but I know that having some tests for your app is important, and not just for the backend part.
Again, Backbone leaves you on your own with this. I’m not talking about bundling a testing framework, because I’d like to be able to choose one (I chose Jasmine because I like RSpec-like syntax), but having some support code to use in tests would be nice. I’ve wasted way too much time trying to make the views behave in tests (models and collections are definitely much easier to test, since they’re much more isolated from each other and from HTML).
You could say that all of this can be provided by plugins, and you would be right. But as the article I’ve mentioned earlier says, every plugin added to the app is a decision you have to make, time that you need to spend researching and learning the API, and it’s always third party code that you have to trust and rely on.
For most of the problems you can encounter there are a few different plugins listed on the plugins page, and there are even a few mini-frameworks built on top of Backbone. But you never know which of them is good until sometime later, when it’s hard to replace it. It would be much better to have built-in solutions instead, that are tested, proven and well integrated with the rest of the pieces.
Where are we going from here
We’ve paused the work on that app for now, so I’m not sure what happens next, but if were to work on it for the next year, I’d definitely consider switching to EmberJS. I think the similarity in the approach to MVC, and the fact that it uses Handlebars which we’ve used here, would make it relatively easy to migrate to, and would save us a lot of support code in the base classes and duplicated code in the subclasses. It would take some time, but I think it would be worth it. Anyway, I’m looking forward to trying out EmberJS in a new project.
The bottom line is, Backbone is much better than what we had before, but since we have more complete solutions now, I’d only use it for simpler sites and applications. For anything moderately complex I’d rather go with Ember or Angular (though I’m still not convinced about some of the choices the latter makes, but a lot of people swear by it).
I’d say that Backbone is to Ember as Sinatra is to Rails: it’s nice if you really need the speed and/or simplicity, but you normally don’t start building a full blown web app on Sinatra unless you have a very good reason (it’s not exactly the same because Ember is still new and unfinished, but I think it will be like this soon). Or, as someone put it, paraphrasing Greenspun:
Any sufficiently complicated Backbone program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Ember.js— Tony Arcieri (@bascule) June 15, 2012