So you’ve accepted the challenge to go thick on the client-side; well done. You’ve considered all the frameworks out there and are unsure which one to choose? You’re not alone. Read on.
My experience, when learning the way of writing client-side apps is proving to be steep and hard. It’s not easy to deliberately choose to use MV*
on the client for someone who wrote JavaScript, based entirely on jQuery and its plugins. This is an entirely new paradigm; it requires basic programming skills and a considerable understanding of JavaScript (the language) design. If your experience relates to mine, then read on!
I will be explaining the main differences between two of the most popular JavaScript clientside frameworks: Backbone.js and Ember.js. Each of these tools has strong points, as well as weaknesses that might help you make a more thoughtful choice.
Disclaimer: as software professionals, we must deal with diversity of opinion. Backbone and Ember are results of opinionated and experienced professionals, like you and me. One tool isn’t better than the other; they just serve different crowds and, ergo, solve different problems. Thanks Trek for the solid advice.
The Philosophy
Backbone is much easier to learn than Ember.
First and foremost, you need to understand that Backbone and Ember particularly serve slightly different crowds. Regarding complexity, Backbone is much easier to learn than Ember. However, it’s said that once you learn Ember, it hardly gets any more complex. Take Trek’s word on it. If you’re just getting started with some real JavaScript, then perhaps Backbone is your tool. If, however, you know that you’re going to deal with a lot more than just a simple use case or two, then you might prefer Ember.
Backbone
Jeremy Ashkenas built Backbone so it would be possible totake the truth out of the DOM
. What he means by this is: whatever business you did using only jQuery / Mootools / Prototype could and should be better extracted into pure JavaScript structures – objects, if you will. Instead of using DOM
elements to define your business elements and behavior, Backbone invites you to do it the other way around. JavaScript objects are the core and the DOM
is merely a representation of that data.
With Backbone, you have some given assertions:
- Data lies in JavaScript objects, not the
DOM
- Event handling lies in JavaScript objects, not jQuery event bindings
- The way you save data in a backend server is done through the objects that contain the data
You are given complete control over the way you build your app. Backbone was meant to give you a basic way of designing your model objects and how these interact with each other through event bindings.
Rendering HTML
to the DOM
is of your responsibility. You are free to choose any template engine: Mustache, DoT, Handlebars, Underscore, etc. Backbone contains a View
prototype that has the responsibility of articulating the DOM
and your JavaScript core.
Ember
When Tilde started building Ember, it did so with a far more challenging goal: to provide standard conventions in client-side development, eliminating as much boilerplate as possible. The result is a much more ambitious framework that aims for a predictable architecture and steady development.
Ember shares some common points with Backbone in the way it tries to pull data and behavior out of the DOM
by providing extendable JavaScript prototypes, but it does this in a very different manner than Backbone does.
Ember stands on:
- Two-way data binding: objects in Ember are able to register bindings between one another. That way, whenever a bound property changes, the other one is updated automatically.
- Computed properties: if you wish to have a property that is a result of a function, you can create them and assign a property as computed by that function.
- Template auto-updates: when an object is updated in your app, all the views currently displayed in the screen that are bound to that object automatically reflect the change, with no boilerplate.
The DOM – Views
Both Backbone and Ember have common key concepts, such as views. They both represent DOM
communication, respectively. The way they accomplish this concept are somewhat different, though.
I’ll use the Todo use case for the examples below, inspired by the TodoMVC showcase.
Backbone
A Backbone View might something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var TaskView = Backbone.View.extend({
tagName : "li"
, template : "task-template"
, render : function () {
// your code to render here.
}
, events : {
"click .mark-done" : "mark_as_done"
, "change .body" : "update_body"
}
, mark_as_done : function () { /* code here */ }
, update_body : function () { /* code here */ }
});
|
This is simply the definition of your view. You will need to instantiate one if you want it to be in the page. Something like this will do the trick:
1
2
|
var task_view = new Task({ model : task_model });
$( "body" ).append(task_view.el);
|
Notice that we’re passing a model in so you can keep a reference to the data object that feeds the template. The template
property inside the view can be used to call an outside template, via an identifier. I’ve used something like this in the past:
1
2
3
4
5
6
7
8
9
|
var TaskView = Backbone.View.extend({
template : "#task-template"
, render : function () {
this .$el.html(
Mustache.render($( this .template).html())
, this .model);
}
// snip
});
|
Ember
Ember has a different approach to views. In fact, the convention states that views should talk to controllers and not models directly. This is a good practice, if you intend to follow a stable architecture. I’ll explain the sample for the same view:
1
2
3
4
5
|
var TaskView = Ember.View.extend({
templateName : "task-template"
, mark_as_done : function () { /* code here */ }
, update_body : function () { /* code here */ }
});
|
That’s it. But where’s all the rendering stuff? Well, Ember lifts that boilerplate for you. Simply say what the template is, the controller that holds the data object, and then you just need to append it to the DOM
.
1
2
3
4
|
var task_view = TaskView.create({
controller : task_controller // Ember.ObjectController
});
task_view.append();
|
When creating a new view instance, it will bind the controller’s content (which can be an Ember.Object
or a list of them) to the view. When you decide to append the view to the DOM
, it will look up the template and place the generated markup for you.
Thoughts
Backbone is more explicit and less magical.
Backbone is more explicit and less magical. You create a View
, tell it what template to use and how, register the events and do what you have to do. They own the page. That’s a great start for those coming from a jQuery background. However, when something needs to be updated in the DOM
, you will face some boilerplate.
With Ember, updates are automatic. You say what template it is and event callbacks are functions inside the view object. Any time an object is updated, the view automatically updates the page.
Some common event bindings are built into Ember and others must be put into the template. It’s good for those who come from a backend perspective, as it reduces boilerplate in a considerable way.
The Data – Models
Models in Backbone and Ember are quite similar. They hold information for a business entity.
Backbone
An example of a Backbone model looks like this:
1
|
var TaskModel = Backbone.Model.extend();
|
With this simple line of code, you have a working model with REST
ful communication built-in. You get methods like save
to persist the data and fetch
to load it for free; no plugin is required. Validation is also built into the way data is saved by providing a validate
callback, which returns a boolean that tells the record to be saved or not. The implementation of the validation is still for the developer to do.
To create a new task, you instantiate a new TaskModel
.
1
2
3
4
|
var task = new TaskModel({
body : "Mow the lawn"
, done : false
});
|
You may inject as many attributes as you like, because the task’s attribute list isn’t strict (think of it as schemaless). You can still set a defaults
property when extending Backbone.Model
.
Ember
With Ember, there are no models, just objects. It might look something like this:
1
|
var TaskObject = Ember.Object.extend();
|
Similar to Backbone, you need to extend from Ember.Object
to create an object class. It inherits all the basic functionality for a class with callbacks for when it gets changed, created and destroyed, amongst other features. It does not, however, have backend communication out of the box. Ember.Data
is being developed as an extension of Ember.Object
by the Ember core team to fulfill that need. It’s already usable but not stable as far as the documentation tells.
Ember objects are also considered to be schemaless. To inject defaults into Ember objects, you extend Ember.Object
by passing an object with as many attributes as you require.
1
2
3
4
|
var TaskObject = Ember.Object.extend({
body : "Mow the lawn"
, done : false
});
|
Thoughts
Backbone has a consolidated way of syncing up with a persistence layer over REST
and that’s a good convention there. It’s one less thing you have to configure in order to work with a backend server.
Ember is working its way toward making Ember.Data
ready for production use, and it looks promising. Even so, the particularity of Ember objects having two way bindings makes it dead easy to perform connections between objects.
At this point in your reading, you have an inflection point between Backbone’s stability in communicating with the backend server and Ember’s bindings. Whatever’s most important to you should determine your decision.
The Glue – Controllers
This is where the frameworks part ways. They have a huge conceptual gap on how to glue things together in your app. While Backbone strives to remain as simple and flexible as possible, Ember sacrifices codebase size for a better architecture. It’s a tradeoff, really.
Warning: the following examples don’t contain HTML template samples.
Backbone
As I noted, Backbone aims for simplicity that converts to flexibility and it achieves such attributes precisely through the lack of a controller class. Most of the workhorse is distributed around views, collections, models and the router (should you choose to use Backbone’s Router
).
Considering a list of tasks that needs to be managed, it would require:
- A
Collection
to store the tasks.
- A
Model
to store a task’s information.
- A
View
to represent the collection.
- Another
View
to represent each task.
- A
Router
to manage URLs.
Most of the application logic will live in the views, as they connect models to the DOM
. There is no clear distinction of responsibilities, as the view does everything. It can be good for small applications that don’t require a solid architecture.
To display a list of tasks, you would end up with something like this:
Collection
1
2
3
|
var TaskList = Backbone.Collection.extend({
model : Task
});
|
Model
1
|
var TaskModel = Backbone.Model.extend();
|
Views
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var TaskListView = Backbone.View.extend({
render: function () {
this .$el.empty();
for (_i = 0, _i < this .collection.length; _i++) {
var task = this .collection.models[_i];
this .$el.append( this .renderItem(task));
}
var tasks = this .$el.html();
this .$el.html(Mustache.to_html(template, {
tasks: tasks,
no_tasks: ! this .collection.length
}));
},
renderItem: function (task) {
var view = new Row({ model: task });
var el = view.render();
return el.el;
},
});
|
1
2
3
4
5
6
7
|
var TaskView = Backbone.View.extend({
tagName: "tr" ,
render: function () {
this .$el.html(M.to_html(template, this .model.attributes));
return this ;
}
});
|
Router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
var Router = Backbone.Router.extend({
initialize: function () {
this .tasks = new TaskList;
this .view = new TaskListView({
collection: this .tasks
});
},
routes: {
"" : "tasks_list" ,
},
tasks_list: function () {
this .view.render();
$( ".bucket:first" ).html( this .view.el);
},
start: function () {
Backbone.history.start({
pushState: true ,
root: "/tickets/"
});
}
});
|
Notice that the collection doesn’t have a template of its own; rather, it delegates to a single task view being rendered and appended to the final result being put on the page.
Ember
The number of classes required to have the same setup is slightly bigger.
- Instead of a
Collection
, you would have an ArrayController
, which works very much alike.
- You would have an extra
ObjectController
for managing a single task.
- Instead of a
Model
, you would have an Object
/ DS.Model
, which work alike.
- You would have the same kind of
View
s.
- A
Router
is also responsible for managing URLs.
You might be thinking that the two frameworks are not too different from one another. It’s rather tempting, but it’s not exactly true. Some particular differences are:
- The controller is responsible for interacting with the data objects, not the View.
- The views are responsible for handling the
DOM
, not the controller.
- The views communicate with the controller, not directly to the data objects.
- The data that feeds the view template is actually a binding to the controller’s data.
- The router is more of a state manager, which includes much more than handling URLs.
The separation of concerns is good in the long term. Controller handles data, views handle the DOM
, period. This kind of decoupled and cohesive, boilerplateless design allows for more focused testability.
The implementation to display the same list of tasks would be something like the following, considering a full Ember application:
Application root architecture
1
2
3
4
5
|
window.App = Ember.Application.create();
App.ApplicationController = Ember.ObjectController.extend();
App.ApplicationView = Ember.View.extend({
templateName: "application"
});
|
Object
1
|
App.Task = Ember.Object.extend();
|
Controllers
1
2
3
|
App.TasksController = Ember.ArrayController.extend({
content: []
});
|
View
1
2
3
|
App.TasksView = Ember.View.extend({
templateName: "my-list"
});
|
Router
1
2
3
4
5
6
7
8
9
|
App.Router = Ember.Router.extend({
root : Ember.Route.extend({
index: Em.Route.extend({
route: '/' ,
connectOutlets: function (router){
router.get( 'applicationController' ).connectOutlet( 'tasks' );
}
})
});
|
In Ember’s case, there’s not much being said about how things are done inside. All of that boilerplate is taken away so you can focus on what really matters in your app: you define a task object, a task list controller with an array called content
, your view and the router simply combines them all together and puts it in the page.
Thoughts
After realizing how Ember really works, it starts to become liberating.
Predictably, this segment was the hardest to grasp on both frameworks. Backbone was definitely easier to learn and its flexible nature gives control over the way objects and DOM
interact. This might be good for you, if you really need that kind of flexibility but still want to maintain a structure for your app’s logic in the JavaScript side.
As for Ember, its breathtaking implementation might be scary at first. However, after realizing how Ember really works, it starts to become liberating. All the conventions the framework sets for you releases you from boilerplate and configuration, letting you focus on your app. This is similar to what Rails did for serverside development that caught so much attention.
What Sets Them Apart?
Ember was meant to lift the common burdens of JavaScript development in the browser.
So far, the whole point of showing the two tools off has been to acknowledge their single and noble purpose: to delegate power to the client-side, through both structure and method.
Backbone core strength is definitely its KISS approach. It provides you with the minimum to let go of the DOM
as the core supporter of your app, and start using real JavaScript objects that can be tested and designed properly.
Backbone comes packed with collections, models, views and the router, amongst other small utilities. You are free to do what you please with them.
Ember, on the other hand, was built with a different mindset, as it aims for a much more conventional and opinionated way of building web apps. It tackles a set of common problems, such as boilerplate, data binding and DOM
templating so you don’t have to worry about them from the start. Ember was meant to lift the common burdens of JavaScript development in the browser.
Ember comes packed with objects, controllers, auto-updating views, state machines, bindings, observers and a router (which is also a state machine), all of them conjured with a good dose of conventions. You have an architecture already designed and ready to begin working without losing focus.
Conclusion
Mind the learning gap. Your experience and cultural heritage will strongly dictate how fast you join the client-side. If you’re scared of what to do or which one to pick, then I struck a nerve of yours and that’s good! Want a good answer on which to choose? Both.
It’s all about the JavaScript
If you’re unsure how even jQuery does all its magic, then start learning Backbone. It’s easier to begin with, and the documentation is dead simple to read and understand. After you’re done, start building something. Go dirty. Check these tutorials if you need some help.
If you’re still in the dark, read Yehuda Katz’s entries on how JavaScript works.
Once you get a better vision of how the JavaScript works as a language, you will begin to gain a better grasp of how the objects interact with each other. When you do, go for Ember. It’s more complicated at first, but don’t give up. Start reading the docs and the guides. You might want to check Trek Glowacki’s blog entry just before getting your hands dirty.
My bottom line
Personally, I’m leaning towards Ember; I enjoy its robustness at a macro scale, and I also prefer its conventions. Backbone is a more malleable and easier tool for smaller apps or small features inside an existing app.
I’m still learning both, and have a few challenges to tackle:
- Automatic tests: how to do them and which testing suite is better. Qunit or Jasmine? Headless (thinking PhantomJS), Node or browser test runner? Not sure yet.
- File uploads
- Internationalization
What are your thoughts on this whole debacle? Do you have any challenges in mind? Any difficulties or impediments? Let me know!