As modern web applications do more and more on the client-side (the fact itself that we now refer to them as “web applications” as opposed to “web sites” is quite telling), there has been rising interest in client-side frameworks. There are a lot of players in this field but for applications with lots of functionality and many moving parts, two of them stand out in particular: Angular.js and Ember.js.

We already published a comprehensive [Angular.js tutorial][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], so we’re going to focus on Ember.js in this post, in which we’ll build a simple Ember application to catalog your music collection. You’ll be introduced to the framework’s main building blocks and get a glimpse into its design principles. Should you want to see the source code while reading, it’s available as rock-and-roll on Github.

What are we going to build?

Here’s what our Rock & Roll app is going to look like in its final version:

Final version of app with ember.js

On the left, you’ll see that we have a list of artists and, on the right, a list of songs by the selected artist (you can also see that I have good taste in music, but I digress). New artists and songs can be added simply by typing in the text box and pressing the adjacent button. The stars beside each song serve to rate it, à la iTunes.

We could break down the app’s rudimentary functionality into the following steps:

  1. Clicking ‘Add’ adds a new artist to the list, with a name specified by the ‘New Artist’ field (same goes for songs for a given artist).
  2. Emptying the ‘New Artist’ field disables the ‘Add’ button (same goes for songs for a given artist).
  3. Clicking an artist’s name lists their songs on the right.
  4. Clicking the stars rates a given song.

We have a long way to go to get this working, so let’s start.

Routes: the key to the Ember.js app

One of the distinguishing features of Ember is the heavy emphasis it puts on URLs. In many other frameworks, having separate URLs for separate screens is either lacking or is tacked on as an afterthought. In Ember, the router—the component that manages urls and transitions between them—is the central piece that coordinates work between building blocks. Consequently, it is also the key to understanding the innerworkings of Ember applications.

Here are the routes for our application:

App.Router.map(function() {
  this.resource('artists', function() {
    this.route('songs', { path: ':slug' });
  });
});

We define a resource route, artists, and a songs route nested inside it. That definition will give us the following routes:

Routes

I used the great Ember Inspector plugin (it exists both for Chrome and Firefox) to show you the generated routes in an easily readable way. Here are the basic rules for Ember routes, which you can verify for our particular case with the help of the above table:

  1. There is an implicit application route.

    This gets activated for all requests (transitions).

  2. There is an implicit index route.

    This gets entered when the user navigates to the root of the application.

  3. Each resource route creates a route with the same name and implicitly creates an index route below it.

    This index route gets activated when the user navigates to the route. In our case, artists.index gets triggered when the user navigates to /artists.

  4. A simple (non-resource), nested route will have its parent route name as its prefix.

    The route we defined as this.route('songs', ...) will have artists.songs as its name. It gets triggered when the user navigates to /artists/pearl-jam or /artists/radiohead.

  5. If the path is not given, it is assumed to be equal to the route name.

  6. If the path contains a :, it is considered a dynamic segment.

    The name assigned to it (in our case, slug) will match the value in the appropriate segment of the url. The slug segment above will have the value pearl-jam, radiohead or any other value that was extracted from the URL.

Display the list of artists

As a first step, we’ll build the screen which displays the list of artists on the left. This screen should be shown to the users when they navigate to /artists/:

Artists

To understand how that screen is rendered, it’s time to introduce another overarching Ember design principle: convention over configuration. In the above section, we saw that /artists activates the artists route. By convention, the name of that route object is ArtistsRoute. It’s this route object’s responsibility to fetch data for the app to render. That happens in the route’s model hook:

App.ArtistsRoute = Ember.Route.extend({
  model: function() {
    var artistObjects = [];
    Ember.$.getJSON('http://localhost:9393/artists', function(artists) {
      artists.forEach(function(data) {
        artistObjects.pushObject(App.Artist.createRecord(data));
      });
    });
    return artistObjects;
  }
});

In this snippet, the data is fetched via an XHR call from the back-end and—after conversion to a model object—pushed to an array which we can subsequently display. However, the responsbilities of the route do not extend to providing display logic, which is handled by the controller. Let’s take a look.

Hmmm—in fact, we do not need to define the controller at this point! Ember is smart enough to generate the controller when needed and set the controller’s M.odel attribute to the return value of the model hook itself, namely, the list of artists. (Again, this is a result of the ‘convention over configuration’ paradigm.) We can step one layer down and create a template to display the list:

<script type="text/x-handlebars" data-template-name="artists">
  <div class="col-md-4">
    <div class="list-group">
      {{#each model}}
        {{#link-to "artists.songs" this class="list-group-item artist-link"}}
          {{name}}
          <span class="pointer glyphicon glyphicon-chevron-right"></span>
        {{/link-to}}
      {{/each}}
    </div>
  </div>
  <div class="col-md-8">
    <div class="list-group">
      {{outlet}}
    </div>
  </div>
</script>

If this looks familiar, it’s because Ember.js uses Handlebars templates, which have a very simple syntax and helpers but do not allow for non-trivial logic (e.g., ORing or ANDing terms in a conditional).

In the above template, we iterate through the model (set up earlier by the route to an array that contains all artists) and for each item in it, we render a link that takes us to the artists.songs route for that artist. The link contains the artist name. The #each helper in Handlebars changes the scope inside it to the current item, so {{name}} will always refer to the name of the artist that is currently under iteration.

Nested routes for nested views

Another point of interest in the above snippet: {{outlet}}, which specifies slots in the template where content can be rendered. When nesting routes, the template for the outer, resource route is rendered first, followed by the inner route, which renders its template content into the {{outlet}} defined by the outer route. This is exactly what happens here.

By convention, all routes render their content into the template of the same name. Above, the data-template-name attribute of the above template is artists which means that it will get rendered for the outer route, artists. It specifies an outlet for the content of the right panel, into which the inner route, artists.index renders its content:

<script type="text/x-handlebars" data-template-name="artists/index">
  <div class="list-group-item empty-list">
    <div class="empty-message">
      Select an artist.
    </div>
  </div>
</script>

In summary, one route (artists) renders its content in the left sidebar, its model being the list of artists. Another route, artists.index renders its own content into the slot provided by the artists template. It could fetch some data to serve as its model but in this case all we want to display is static text, so we don’t need to.

Create an artist

Part 1: Data binding

Next, we want to be able to create artists, not just look at a boring list.

When I showed that artists template that renders the list of artists, I cheated a bit. I cut out the top part to focus on what’s important. Now, I’ll add that back:

<script type="text/x-handlebars" data-template-name="artists">
  <div class="col-md-4">
    <div class="list-group">
      <div class="list-group-item">
        {{input type="text" class="new-artist" placeholder="New Artist" value=newName}}
        <button class="btn btn-primary btn-sm new-artist-button" {{action "createArtist"}}
          {{bind-attr disabled=disabled}}>Add</button>
      </div>
    < this is where the list of artists is rendered >
  ...
</script>

We use an Ember helper, input, with type text to render a simple text input. In it, we bind the value of the text input to the newName property of the controller that backs up this template, ArtistsController. By consequence, when the value property of the input changes (in other words, when the user types text into it) the newName property on the controller will be kept in sync.

We also make it known that the createArtist action should be fired when the button is clicked. Finally, we bind the disabled property of the button to the disabled property of the controller. So what does the controller look like?

App.ArtistsController = Ember.ArrayController.extend({
  newName: '',
  disabled: function() {
    return Ember.isEmpty(this.get('newName'));
  }.property('newName')
});

newName is set to empty at the start which means that the text input is going to be blank. (Remember what I told about bindings? Try to change newName and see it get reflected as the text in the input field.)

disabled is implemented such that when there is no text in the input box, it is going to return true and thus the button will be disabled. The .property call at the end makes this a “computed property”, another scrumptious slice of the Ember cake.

Computed properties are properties that depend on other properties, which can themselves be “normal” or computed. Ember caches the value of these until one of the dependent properties change. It then recalculates the value of the computed property and caches it again.

Here’s a visual representation of the above process. To summarize: when the user inputs an artist’s name, the newName property updates, followed by the disabled property and, finally, the artist’s name is added to the list.

Detour: A single source of truth

Think about that for a moment. With the help of bindings and computed properties, we can establish (model) data as the single source of truth. Above, a change in the name of the new artist triggers a change in the controller property, which in turn triggers a change in the disabled property. As the user starts to type the name of the new artist, the button becomes enabled, as if by magic.

The bigger the system, the more leverage we gain from the ‘single source of truth’ principle. It keeps our code clean and robust, and our property definitions, more declarative.

Some other frameworks also put emphasis on having model data be the single source of truth but either do not go as far as Ember or fail to do such a thorough a job. Angular, for example, has two-way bindings—but does not have computed properties. It can “emulate” computed properties through simple functions; the problem here is that it has no way of knowing when to refresh a “computed property” and thus resorts to dirty checking and, in turn, leads to a performance loss, especially notable in bigger applications.

Should you wish to learn more about the topic, I recommend you read eviltrout’s blog post for a shorter version or this Quora question for a lengthier discussion in which core developers from both sides weigh in.

Part 2: Action handlers

Let’s go back to see how the createArtist action is created after it’s fired (following the press of the button):

App.ArtistsRoute = Ember.Route.extend({
  ...
  actions: {
    createArtist: function() {
      var name = this.get('controller').get('newName');

      Ember.$.ajax('http://localhost:9393/artists', {
        type: 'POST',
        dataType: 'json',
        data: { name: name },
        context: this,
        success: function(data) {
          var artist = App.Artist.createRecord(data);
          this.modelFor('artists').pushObject(artist);
          this.get('controller').set('newName', '');
          this.transitionTo('artists.songs', artist);
        },
        error: function() {
          alert('Failed to save artist');
        }
      });
    }
  }
});

Action handlers need to be wrapped in an actions object and can be defined on the route, controller or view. I chose to define it on the route here because the result of the action is not confined to the controller but rather, “global”.

There is nothing fancy going on here. After the back-end informed us that the saving operation completed successfully, we do three things, in order:

  1. Add the new artist to the model of the template (all artists) so that it gets re-rendered and the new artist appears as the last item of the list.
  2. Clear the input field via the newName binding, saving us from having to manipulate the DOM directly.
  3. Transition to a new route (artists.songs), passing in the freshly created artist as the model for that route. transitionTo is the way to move between routes internally. (The link-to helper serves to do that via user action.)

Display songs for an artist

We can display the songs for an artist either by clicking on the artist’s name. We also pass in the artist that is going to become the model of the new route. If the model object is thusly passed in, the model hook of the route will not be called since there is no need to resolve the model.

The active route here is artists.songs and thus the controller and template are going to be ArtistsSongsController and artists/songs, respectively. We already saw how the template gets rendered into the outlet provided by the artists template so we can focus on just the template at hand:

<script type="text/x-handlebars" data-template-name="artists/songs">
  (...)
  {{#each songs}}
    <div class="list-group-item">
      {{title}}
      {{view App.StarRating maxRating=5}}
    </div>
  {{/each}}
</script>

Note that I stripped out the code to create a new song as it would be exactly the same as that for creating a new artist.

The songs property is set up in all artist objects from the data returned by the server. The exact mechanism by which it is done holds little interest to the current discussion. For now, it’s sufficient for us to know that each song has a title and a rating.

The title is displayed directly in the template and the rating gets represented by stars, via the StarRating view. Let’s see that now.

Star rating widget

A song’s rating falls between 1 and 5 and gets shown to the user through a view, App.StarRating. Views have access to their context (in this case, the song) and their controller. This means they can read and modify its properties. This is in contrast to another Ember building block, components, which are isolated, reusable controls with access to only what has been passed in to them. (We could use a star rating component in this example, too.)

Let’s see how the view displays the number of stars and sets the song’s rating when the user clicks on one of the stars:

App.StarRating = Ember.View.extend({
  classNames: ['rating-panel'],
  templateName: 'star-rating',

  rating: Ember.computed.alias('context.rating'),
  fullStars: Ember.computed.alias('rating'),
  numStars:  Ember.computed.alias('maxRating'),

  stars: function() {
    var ratings = [];
    var fullStars = this.starRange(1, this.get('fullStars'), 'full');
    var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty');
    Array.prototype.push.apply(ratings, fullStars);
    Array.prototype.push.apply(ratings, emptyStars);
    return ratings;
  }.property('fullStars', 'numStars'),

  starRange: function(start, end, type) {
    var starsData = [];
    for (i = start; i <= end; i++) {
      starsData.push({ rating: i, full: type === 'full' });
    };
    return starsData;
  },
  (...)
});

rating, fullStars and numStars are computed properties which we discussed previously with the disabled property of the ArtistsController. Above, I used a so-called computed property macro, about a dozen of which are defined in Ember. They make typical computed properties more succinct and less error-prone (to write). I set rating to be the rating of the context (and thus the song), while I defined both the fullStars and numStars properties so that they read better in the context of the star rating widget.

The stars method is the main attraction. It returns an array of data for the stars in which each item contains a rating property (from 1 to 5) and a flag (full) to indicate whether the star is full. This makes it exceedingly simple to walk through them in the template:

<script type="text/x-handlebars" data-template-name="star-rating">    
  {{#each view.stars}}
    <span {{bind-attr data-rating=rating}}
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating" target=view}}>
    </span>
  {{/each}}
</script>

This snippet contains several points of note:

  1. First, the each helper designates that it uses a view property (as opposed to a property on the controller) by prefixing the property name with view.
  2. Second, the class attribute of the span tag has mixed dynamic and static classes assigned. Anything prefixed by a : becomes a static class, while the full:glyphicon-star:glyphicon-star-empty notation is like a ternary operator in JavaScript: if the full property is truthy, the first class should be assigned; if not, the second.
  3. Finally, when the tag is clicked, the setRating action should be fired—but Ember will look it up on the view, not the route or controller, as in the case of creating a new artist.

The action is thus defined on the view:

App.StarRating = Ember.View.extend({
  (...)
  actions: {
    setRating: function() {
      var newRating = $(event.target).data('rating');
      this.set('rating', newRating);
    }
  }
});

We get the rating from the rating data attribute we assigned in the template and then set that as the rating for the song. Note that the new rating is not persisted on the back-end. It would not be difficult to implement this functionality based on how we created an artist and is left as an exercise for the motivated reader.

Wrapping it all up

We have tasted several ingredients of the aforementioned Ember cake:

  • We have seen how routes are the crux of Ember applications and how they serve as the basis of naming conventions.
  • We have seen how two-way data bindings and computed properties make our model data the single source of truth and allow us to avoid direct DOM manipulation.
  • And we have seen how to fire and handle actions in several ways and build a custom view to create a control that is not part of our HTML.

Beautiful, isn’t it?

Further reading (and watching)

There’s far more to Ember than I could fit in this post alone. If you’d like to see a screencast series on how I built a somewhat more developed version of the above application and/or learn more about Ember, you can sign up to my mailing list to get articles or tips on a weekly basis.

I hope I’ve whetted your appetite to learn more about Ember.js and that you go well beyond the sample application I’ve used in this post. As you continue to learn about Ember.js, be sure to glance at our article on Ember Data to learn how to use the ember-data library. Have fun building!

About the author

Balint Erdi, Hungary
member since August 26, 2013
Balint lives on the cutting edge of technology, and has been practicing TDD since before it became popular. He was a classic PHP developer, and has since moved on to Java, Python, and Ruby. Currently, he is in love with Ember.js. He is fascinated by functional programming and also enjoys Clojure. [click to continue...]
Hiring? Meet the Top 10 Freelance JavaScript Developers for Hire in September 2016
Don't miss out.
Get the latest updates first.
No spam. Just great engineering and design posts.
Don't miss out.
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

Comments

Jesus Rodriguez
I am not an Ember user (I entered here by twitter) but I like your blog theme and content. I just spotted two typos. In the points 3 and 4 (of the bold enumeration) you got a "artisttist" and a "songgs". I will read it better later :)
Dan Kuthy
Thanks for spotting those typos, Jesus. We've fixed them.
Sean McCleary
Since you mentioned writing an article on angular, I was curious on your thoughts on how ember compares to angular. That might be too broad of a question. I guess if you have any additional thoughts on comparison, I'd be interested in hearing them.
Jesus Rodriguez
I am angular developer and this kind of discussions doesn't lead to anywhere. Both are good frameworks and both works perfect for almost any use case :)
Sean McCleary
I'm not trying to start a holy war. I am legitimately interested in what someone who has used both would have to say. I don't know enough about either framework to make an educated decision on which one to choose, and that's thoughts from people who have used both are really important to me.
ericbae
That animated illustration was absolutely awesome! How did you create that! Plus, good thorough tute on Ember.js, will refer to your article once I want to pick it up again!
Balint Erdi
Thank you, Eric! The illustration was made by a Toptal designer, and I also like how it turned out.
Balint Erdi
Hey Sean, Robin Ward, one of the core developers of Discuss, the largest OS Ember app, did a comparison: http://fishtank.eviltrout.com/t/angularjs-vs-ember/20 , but although he did not intend to, he did start a holy war. Nevertheless, it is definitely worth a read. The other resource I recommend is by Alex Matchneer, a core Ember.js contributor. It is a presentation he gave and can be found here: https://docs.google.com/presentation/d/1e0z1pT9JuEh8G5DOtib6XFDHK0GUFtrZrU3IfxJynaA/preview#slide=id.p If anyone can point to such resources done by someone who is more experienced with Angular, I would like to read that, too.
Balint Erdi
Thank you!
Paul Cook
Balint: Your screencasts really helped me take a big step in finally understanding ember. Great job and Thanks!
Balint Erdi
Hey Paul, I'm really glad they did. Enjoy the journey.
Diogo Moretti
Ow... Ember + Pearl Jam! =) Thanks!!!!
Balint Erdi
A killer mix, indeed ;)
kimroen
Thank you for this, I found the View-part especially enlightening. I just wanted to point out that in your createArtist-action, where you write: var name = this.get('controller').get('newName'); You can actually combine the two "get"s there into: this.get('controller.newName'); which is very convenient, I find :)
Luboš Volkov
Hi, the image was crafted in the photoshop and then part by part animated in the After Effects! Glad you like it btw.
Balint Erdi
Indeed, I could have written like that, I like the short-hand syntax, too. Can't remember if it was a deliberate decision (so I do not add extra complexity, even if minimal, that I need to explain) or I just forgot to use it, probably the latter :) If you liked the view part, you might be interested in a mini-series I did to convert the star-rating view to a component. http://balinterdi.com/2014/02/05/convert-a-view-into-a-component.html http://balinterdi.com/2014/02/12/making-an-emberjs-component-more-reusable.html http://balinterdi.com/2014/02/18/readers-letters-making-an-ember-dot-js-component-even-better.html
Evan Wieland
Yeah, great .gif!
Raka Angga Jananuraga
Hi, Thanks for the article. It's one of the best around. However one thing is still unclear to me. I get it that we load the list of artists in the ArtistsIndexRoute..., but... what should / can we do in the ArtistsRoute? (if we're even allowed to implement it... or should we just ignore it?).
Bràñsøñ G Kürīå
hey is this on github?....
Rick Grashel
This is a fantastic tutorial. For folks who still use classic Java web frameworks, I created an implementation of this Rock-and-Roll Ember application using the Stripes framework and Stripes REST services. https://github.com/rgrashel/stripes-ember Great stuff, Balint!
Justin Barca
Its nice to see some more focus on data in an Ember tutorial, it's something I've struggled to find recently. However, I feel doubtful about this example helping a total noob like me write workable code. I think when writing examples its important that the code could actually run, so it would be nice to see a repo where one could run and tinker with this app, whereas this seems a bit more like pseudocode. You use a pushObject on an ordinary JS array, that doesn't seem right. As I've struggled to get Adapters working properly with fixtures, I like the idea of just using a getJSON in the route, however your return statement for the ArtistsRoute is outside an async call, thus would it not return an empty array ? Anyway, thanks for taking the time to share :)
Ramchand
Nice demonstration! It set me up to develop in Ember. Though, there is a minor bug that I found. In the stars function, while getting the value for emptyStars var, this.get('fullStars')+1 gives the value as a string. Instead, wrapping it with parseInt() makes it fine.
Balint Erdi
It sure is: https://github.com/balinterdi/rock-and-roll
Balint Erdi
Hey Justin, This article is more than a year old now and lots of things have changed in Ember since (the most important among them is that using globals, like App.IndexRoute, is considered bad practice) but let me try to address your questions. 1. Array.pushObject works because Ember extends prototypes, and adds that method to Array.prototype. 2. `artistObjects` indeed returns just an empty array because the XHR request happens asynchronously but once it returns the data, `artistObjects` get populated and thus the template rerenders with the returned bands. Also, since posting this, I've expanded the application, transformed it to use Ember CLI, the latest Ember and best practices and released it in book form: http://balinterdi.com/rock-and-roll-with-emberjs/ .
Balint Erdi
Thank you, Rick!
Balint Erdi
That depends on your app. Usually, you can return all the bands in ArtistsRoute and then use `this.modelFor('artists')` in the ArtistsIndexRoute and other routes below ArtistsRoute in the route hierarchy to get the list of all bands. The post is very old, so examples should now use Ember CLI and ES6 modules so today instead of ArtistsRoute we'd be talking about the `app/routes/artists.js` module but the concept stays the same.
comments powered by Disqus