Andy Crum

An Introduction to Ember.js

Ember.js describes itself as a “framework for creating ambitious web applications.” In the front-end world, it’s often compared to Angular.js or Backbone.js. If you’re interested in Ember, it will help you to realize right away that it’s pretty different from either of those options. I’ll go over a little bit of why it’s different, how to get started, and show a quick sample application using fixture data.

Convention Over Configuration

Ember is a convention-over-configuration, Rails-esque take on the client-side framework. Ember wholly embraces the framework concept. The core team has decided to figure out the most common patterns used when developing web applications, and has created a framework that allows developers to quickly and easily write code using conventions that make sense. Compared to Backbone or Angular, you’ll end up writing much less boilerplate code. A lot of the work is done by Ember behind the scenes; some people like that, some do not — your mileage may vary.

If you want to see the specific differences in a sample application, check out TodoMVC, a collection of implementations of the same “Todo” app in many different libraries/frameworks.

MVC…ish

Ember adheres pretty closely to the Model-View-Controller pattern (though few things are purely “MVC” in the classic sense). I’ve heard Ember described as MVC+R, because the router is a big part of the value Ember provides and is pretty integral to the framework. A typical Ember application will have lots of models, views, controllers, and routes – but also components, helpers, and more. There’s a lot to learn, so let’s get started.

Getting Started

There is an “Ember Way” of doing almost anything when building a web application. The learning curve can be steep at the beginning while you figure out exactly what the “Ember Way” is, but once you’re there, the productivity gains are worth it.

So how do you get there? The official guides are where you should begin, but here’s a list of other helpful resources:

A Sample Application

Let’s walk through a small sample application. I’m not going to go through every step of setting up Ember; this is just intended to give you a look at what Ember code looks like and how certain pieces of the framework work together.

One day, in my entrepreneurial stupor, maybe I decide jukeboxes are broken. I’m going to create the best jukebox of all time in Ember.js. It’s going to be so amazing. Let’s change the world!

Jukebox = Ember.Application.create();

This line just initializes the application. Jukebox is the name of your application (you’ll use it a lot), and it can be whatever you want it to be. The world is your oyster.

Now that our application has been created, let’s go ahead and define our models. I usually like to do this at this point since it’s the basis for the rest of the application.

So what will they be? Well, jukeboxes have songs, and songs have a name, an artist, and a duration. So we’ll go with that for now.

// Model
Jukebox.Song = DS.Model.extend({
  name: DS.attr('string'),
  artist: DS.attr('string'),
  duration: DS.attr('number')
});

There are a few things happening here. DS is the namespace for Ember Data. Let’s talk about that for a second.

A Quick Aside About Ember Data

The Ember guides describe Ember Data’s purpose here:

Ember Data is a library that integrates tightly with Ember.js to make it easy to retrieve records from a server, cache them for performance, save updates back to the server, and create new records on the client.

Ember Data comes with several built-in adapters, depending on the data it expects to be returned from the server. You don’t have to use Ember Data with Ember (many people have written custom libraries for this purpose), but it does make it very easy to get started1. I plan on writing more about getting started with Ember Data very soon, so I won’t go into too much detail this time around.

Another Quick Aside — The Ember Inspector

If you’re going to do any development in Ember, you should immediately download the Ember Inspector extension for Chrome (or Firefox, if that’s your thing). It is absolutely invaluable when debugging your code, and can also be a great learning tool — just go to an existing site built on Ember and fire it up, and you’ll be able to see how other Ember users have structured parts of their applications. Okay… back to the jukebox!

Back to the Jukebox

For now, we’ll use Ember Data’s DS.FixtureAdapter and include some fixture data for our Jukebox.Song model. Fixture data is just an array of objects.

Jukebox.Song.FIXTURES = [
  { id: 1, name: 'Blue Ridge Mountains', duration: 267, artist: 'Fleet Foxes' },
  { id: 2, name: 'White Winter Hymnal', duration: 149, artist: 'Fleet Foxes' }
];

This looks good, except… we’ve changed our mind about something. It looks like we’ll be repeating ourselves a lot if we have to include the artist name on every song. We also might want to store some additional information about artists. What if we created a Jukebox.Artist model? Let’s replace the old Jukebox.Song model definition with new definitions:

// Models

Jukebox.Song = DS.Model.extend({
  name: DS.attr('string'),
  artist: DS.belongsTo('artist'),
  duration: DS.attr('number')
});

Jukebox.Artist = DS.Model.extend({
  name: DS.attr('string'),
  genre: DS.attr('string'),
  songs: DS.hasMany('song')
});

What we’ve done here is create relationships. Ember Data has two types of relationships: DS.hasMany and DS.belongsTo. It works exactly how you would expect it to2. Let’s edit our Song fixtures and add new fixture data for Jukebox.Artist:

Jukebox.Song.FIXTURES = [
  { id: 1, name: 'Blue Ridge Mountains', duration: 267, artist: 1 },
  { id: 2, name: 'White Winter Hymnal', duration: 149, artist: 1 }
];

Jukebox.Artist.FIXTURES = [
  { id: 1, name: 'Fleet Foxes', genre: 'Folk', songs: [1, 2] }
];

One artist, two songs. Good enough for now. Now that the models are taken care of, let’s look at our routes. In most applications, we’ll have a route map (very similar to Rails’ routes.rb file) where we define our app’s resources and routes. We don’t need it in this application yet, so we’ll skip it. You can learn more about the router from the official Ember guides.

Ember applications come with a built-in ApplicationRoute and IndexRoute. We’ll just use IndexRoute for now.

So what do we need for our jukebox route? For now, we’ll just make it display a list of available songs.

Jukebox.IndexRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('song');
  }
});

The model method on a route returns an Object or Promise representing the model you’d like to use for that route. Since we want to display a list of songs on this route’s view, we pass in the song models. this.store.find is Ember Data-specific, but you could also just return an array of Objects from the model method.

Now that we have our models, fixtures, and route, we should decide how we want our jukebox to look. Sounds like a job for templates! Ember uses an extended version of Handlebars for its templates. Here’s what the index template will look like to begin with:

<h3>Available Songs</h3>
<ul>
{{#each}}
  <li>{{name}} ({{duration}}) - {{artist.name}}</li>
{{/each}}
</ul>

The each block loops through the models that are passed in by the route, and for each one displays a list item with that object’s properties. You probably noticed that I am including artist.name in the list item. Because artist is a related model (each song belongsTo an artist), you can pass in the related model name and display any of its properties as well.

So now we have all of the pieces to display our songs. When we list them out, it looks like this:

Blue Ridge Mountains (267) - Fleet Foxes
White Winter Hymnal (149) - Fleet Foxes

Hmm. That’s okay I guess, but we store duration as an int (the total number of seconds), and it’s a little strange to have that be shown in the list instead of the more common ‘minutes:seconds’ format. Handlebars lets you define Helpers, which Ember has also extended to let you pass in properties. A helper is just a function that takes zero or more parameters and returns something else (usually a modified version of whatever you are passing in.) Here’s the duration-to-time helper:

Ember.Handlebars.registerBoundHelper('duration-to-time', function(seconds) {
  var minutes = seconds > 60? parseInt(seconds/60, 10) : 0,
      leftOverSeconds = seconds > 60? seconds % (minutes * 60) : seconds;

  leftOverSeconds = (leftOverSeconds < 10)? '0'+leftOverSeconds : leftOverSeconds;
  return minutes + ':' + leftOverSeconds;
});

This isn’t the cleanest code, but it does what we need for now. In our template, we should now replace the {{duration}} call with {{duration-to-time duration}}. You can see that we are calling the helper first, then adding a space and passing in duration as a parameter to the helper. Now our song list looks like this:

Blue Ridge Mountains (4:27) - Fleet Foxes
White Winter Hymnal (2:29) - Fleet Foxes

Awwwww yeah. But this is kind of boring. Just a list of songs? Can’t our jukebox at least let us pick a song and play it? Okay, fine. Let’s add a button next to every song to play it.

<h3>Available Songs</h3>
<ul>
{{#each}}
  <li>
    {{name}} ({{duration-to-time duration}}) - {{artist.name}} <button {{action 'playSong' this}}>Play</button>
  </li>
{{/each}}
</ul>

You’ll might notice that I’ve added an action block inside of the button element. This is Ember’s built-in action helper. The action helper is a very big part of Ember — it ties user interaction, like clicking in the views, to the controllers. In this case, I’ve said that this button should call the action playSong and it should pass in this, which is just a reference to the current song model.

Since we are going to call the playSong action, we should create it somewhere. Enter the mighty Ember.Controller. Until this point, our application has been using the automatically generated IndexController, which has worked wonderfully, because we haven’t really defined any interactions. But now we need something more. What should the playSong action do? For now, it’ll just set a property called nowPlaying to the current song. Here’s what the controller looks like:

Jukebox.IndexController = Ember.ArrayController.extend({
  nowPlaying: null,
  actions: {
    playSong: function(song) {
      this.set('nowPlaying', song);
    }
  }
});

First we initialize nowPlaying to null, then we define an actions hash with our playSong action inside of it. If we want to add any other actions (say, deleteSong), we can also add those here. Since we are passing in the song object via the template, we can set the controller’s nowPlaying property to just point to the song object.

That’s great, but how do you see what’s playing? We need to update our template. Here’s the new index template:

Now Playing:
<strong>
{{#if nowPlaying}}
  {{nowPlaying.artist.name}} - {{nowPlaying.name}} ({{duration-to-time nowPlaying.duration}})
{{else}}
-
{{/if}}
</strong>

<h3>Available Songs</h3>
<ul>
{{#each}}
  <li>{{name}} ({{duration-to-time duration}}) - {{artist.name}} <button {{action 'playSong' this}}>Play</button></li>
{{/each}}
</ul>

We use the Handlebars if helper to check nowPlaying, and if it’s not empty, the jukebox shows some information about the song. nowPlaying is just a song object, so we have access to the same properties that we have anywhere else where we pass in songs.

Success!

Our jukebox now has a couple songs and lets you click a button to play whichever song you’d like (even if it doesn’t actually play, it’s fun to pretend… right?) You can find the source code and a working example of this on jsbin here. Side note: emberjs.jsbin.com. It will be invaluable to you.

I barely scratched the surface here, and there are many more in-depth Ember topics to discuss, but for now hopefully this gave you a good introduction to Ember. Go ahead and check out the official guides, then get started on your Ember app!


  1. As of this writing, Ember Data has not finalized version 1.0, and the core team recently said that a couple API changes will happen before Ember Data hits 1.0.

  2. For more details on relationships in Ember Data, check out this section of the official guides.

Do you have questions or comments about something I wrote here? Let me know at @andy_crum or andy@andycrum.com!