Introduction
The JavaScript application landscape is evolving rapidly. Just a few years ago, using JavaScript to build complex single-page applications seemed impractical. Today, we have frameworks competing to provide the best developer experience and most powerful features for building what we once thought impossible in the browser.
I've been exploring Ember.js over the past few months, and I'm impressed by its ambitious approach to solving common problems in JavaScript application development. Unlike minimalist libraries that give you just the basics, Ember provides a comprehensive solution with strong opinions about how applications should be structured. This opinionated approach might not appeal to everyone, but for teams building complex applications, having these decisions made for you can be liberating.
Ember.js emerged from the SproutCore 2.0 project and was officially renamed to Ember in December 2011. Created by Yehuda Katz and Tom Dale—both well-known contributors to Ruby on Rails and jQuery—Ember brings many of the conventions and philosophies from Rails to the JavaScript world. The framework is designed for building what the team calls "ambitious web applications"—applications with rich user interfaces and complex state management.
What is Ember.js?
Ember.js is a complete MVC framework for building single-page web applications. Unlike lighter-weight libraries like Backbone.js that provide just the basic building blocks, Ember includes everything you need: a router, templating system, data binding, computed properties, and a clear application structure.
The framework is built around several core principles:
Convention over Configuration: Ember makes assumptions about how your application should be structured. If you follow the conventions, things "just work" with minimal configuration. This reduces decision fatigue and helps teams maintain consistency.
Developer Productivity: Ember includes features like automatic data binding and computed properties that eliminate boilerplate code. You describe what your UI should look like, and Ember handles keeping it synchronized with your data.
Scalability: The framework is designed for applications that grow. Its patterns and abstractions scale from simple prototypes to complex, feature-rich applications without requiring rewrites.
Stability: While still pre-1.0, Ember aims for API stability to avoid breaking changes that would require updating your code with every release.
At its core, Ember provides:
- Models: Objects representing your application's data
- Views: Components that render templates and handle user interaction
- Controllers: Objects that decorate models with display logic
- Router: A state machine for managing application state and URLs
- Templates: Handlebars-based templates with built-in helpers and bindings
- Observers and Bindings: Automatic synchronization between data and UI
Why Use Ember.js?
Automatic Data Binding
The most powerful feature of Ember is its two-way data binding system. When data changes, the UI updates automatically. When users interact with the UI, the data updates automatically. You don't write code to manually synchronize these two representations—Ember handles it for you.
This eliminates entire categories of bugs where the UI and data get out of sync. It also makes your code dramatically more concise since you're not constantly writing update handlers.
Computed Properties
Ember's computed properties let you define values derived from other properties. When the source properties change, computed properties automatically recalculate. This declarative approach makes it easy to build complex UIs that stay synchronized without manual intervention.
App.Person = Ember.Object.extend({
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
var person = App.Person.create({
firstName: 'John',
lastName: 'Doe'
});
person.get('fullName'); // "John Doe"
person.set('firstName', 'Jane');
person.get('fullName'); // "Jane Doe" - automatically updated
The fullName property depends on firstName and lastName. Ember knows this from the .property() declaration and recalculates fullName whenever either dependency changes.
Handlebars Templates
Ember uses Handlebars for templating, which provides a clean separation between logic and presentation. Handlebars templates are logic-less HTML with special expressions in double curly braces:
<div class="person">
<h2>{{fullName}}</h2>
<p>Email: {{email}}</p>
<p>Member since: {{memberSince}}</p>
</div>
The template automatically updates when the bound properties change. Handlebars also provides helpers for common patterns like iteration and conditionals:
{{#if isAdmin}}
<button>Admin Panel</button>
{{/if}}
<ul>
{{#each users}}
<li>{{name}}</li>
{{/each}}
</ul>
State Management with Router
Ember's router is more sophisticated than simple URL routing. It's a state machine that manages your entire application's state. URLs map to states, and states can have nested substates, creating a hierarchy that mirrors your UI structure.
This approach makes it easy to handle complex application flows, manage loading states, and ensure deep linking works correctly. The router also handles browser history, making back/forward buttons work naturally.
Strong Conventions
Ember enforces naming conventions that connect different parts of your application. A PostController automatically works with a Post model and looks for a post template. These conventions reduce configuration and make it easy to understand how pieces fit together.
For Rails developers, this feels familiar. For others, it might feel restrictive at first, but the consistency pays dividends as applications grow.
Core Concepts
Let's explore Ember's main components and how they work together.
Models
Models represent your application's data. In Ember, models are simple JavaScript objects that extend Ember.Object:
App.Post = Ember.Object.extend({
title: null,
body: null,
author: null,
publishedAt: null,
isPublished: function() {
return this.get('publishedAt') != null;
}.property('publishedAt'),
summary: function() {
var body = this.get('body');
return body.substring(0, 200) + '...';
}.property('body')
});
Models can have computed properties that derive values from other properties. Ember tracks dependencies automatically and updates computed properties when their dependencies change.
To create an instance:
var post = App.Post.create({
title: 'Getting Started with Ember.js',
body: 'Ember is a framework for building ambitious web applications...',
author: 'John Doe',
publishedAt: new Date()
});
console.log(post.get('isPublished')); // true
console.log(post.get('summary')); // First 200 characters...
Note the use of get() and set() methods. Ember requires these accessor methods to track property access and trigger bindings. While it feels verbose at first, it's essential for Ember's binding system to work.
Views
Views in Ember handle rendering and user interaction. They manage a portion of the DOM and respond to events:
App.PostView = Ember.View.extend({
templateName: 'post',
tagName: 'article',
classNames: ['post'],
classNameBindings: ['isPublished'],
isPublished: function() {
return this.get('content.publishedAt') != null;
}.property('content.publishedAt'),
click: function(event) {
this.get('controller').send('showPost', this.get('content'));
}
});
This view:
- Renders the
posttemplate - Creates an
<article>element with classpost - Adds class
is-publishedwhen the post is published (viaclassNameBindings) - Sends a
showPostaction to the controller when clicked
Views can also define custom event handlers for any DOM event:
App.EditableView = Ember.View.extend({
doubleClick: function() {
this.set('isEditing', true);
},
focusOut: function() {
this.set('isEditing', false);
}
});
Controllers
Controllers decorate models with display logic and handle user actions. They sit between views and models, providing a place for presentation concerns that don't belong in the model:
App.PostController = Ember.ObjectController.extend({
isEditing: false,
formattedDate: function() {
var date = this.get('publishedAt');
if (!date) return 'Not published';
return date.toLocaleDateString();
}.property('publishedAt'),
edit: function() {
this.set('isEditing', true);
},
save: function() {
// Save the post
this.set('isEditing', false);
},
cancel: function() {
// Rollback changes
this.set('isEditing', false);
}
});
Ember provides two types of controllers:
- ObjectController: Proxies a single model object
- ArrayController: Proxies an array of models
Controllers proxy property access to their content, so you can write {{title}} in a template instead of {{content.title}}.
Templates and Handlebars
Ember templates use Handlebars, a logic-less templating system. Templates are written in HTML with special expressions:
<article class="post">
<h1>{{title}}</h1>
<div class="meta">
By {{author}} on {{formattedDate}}
</div>
{{#if isEditing}}
<div class="editor">
{{view Ember.TextField valueBinding="title"}}
{{view Ember.TextArea valueBinding="body"}}
<button {{action "save"}}>Save</button>
<button {{action "cancel"}}>Cancel</button>
</div>
{{else}}
<div class="content">
{{body}}
</div>
<button {{action "edit"}}>Edit</button>
{{/if}}
</article>
This template demonstrates several Ember template features:
- Property binding:
{{title}}automatically updates when title changes - Conditionals:
{{#if isEditing}}shows/hides content - Built-in views:
Ember.TextFieldandEmber.TextAreacreate form inputs - Two-way binding:
valueBinding="title"binds input value to the property - Actions:
{{action "edit"}}sends theeditaction to the controller when clicked
Handlebars also supports iteration:
<ul class="posts">
{{#each post in controller}}
<li>
<h2>{{post.title}}</h2>
<p>{{post.summary}}</p>
</li>
{{/each}}
</ul>
Router and State Management
The router manages application state and maps URLs to application states. Here's a simple router configuration:
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('posts');
}
}),
post: Ember.Route.extend({
route: '/posts/:post_id',
connectOutlets: function(router, post) {
router.get('applicationController').connectOutlet('post', post);
}
})
})
});
This router defines two states:
indexat/that shows a list of postspostat/posts/:post_idthat shows a single post
The connectOutlets method tells Ember what to display in each state. When the application enters a state, Ember automatically:
- Updates the URL
- Instantiates the appropriate controller
- Renders the corresponding template
- Manages the browser history
Navigation between states is simple:
App.router.transitionTo('post', post);
Ember handles the URL change, state transition, and UI update automatically.
Building a Complete Application
Let's build a simple blog application to see how all these pieces work together.
Application Setup
First, set up the basic application structure:
<!DOCTYPE html>
<html>
<head>
<title>Ember Blog</title>
<script src="jquery.js"></script>
<script src="handlebars.js"></script>
<script src="ember.js"></script>
</head>
<body>
<script type="text/x-handlebars">
{{outlet}}
</script>
<script src="app.js"></script>
</body>
</html>
The {{outlet}} is a placeholder where Ember renders the active state's template.
Define the Application
// app.js
window.App = Ember.Application.create();
This creates the Ember application instance. Ember automatically detects views, controllers, and models defined in the App namespace.
Create the Model
App.Post = Ember.Object.extend({
title: null,
body: null,
author: null,
publishedAt: null,
isPublished: function() {
return this.get('publishedAt') != null;
}.property('publishedAt'),
excerpt: function() {
var body = this.get('body') || '';
return body.substring(0, 200);
}.property('body')
});
// Sample data
App.posts = [
App.Post.create({
id: 1,
title: 'Introduction to Ember.js',
body: 'Ember.js is a framework for creating ambitious web applications...',
author: 'Jane Smith',
publishedAt: new Date('2012-08-15')
}),
App.Post.create({
id: 2,
title: 'Understanding Data Binding',
body: 'Data binding is one of the most powerful features in Ember...',
author: 'John Doe',
publishedAt: new Date('2012-09-01')
})
];
Create Controllers
App.ApplicationController = Ember.Controller.extend();
App.PostsController = Ember.ArrayController.extend({
sortProperties: ['publishedAt'],
sortAscending: false
});
App.PostController = Ember.ObjectController.extend({
isEditing: false,
edit: function() {
this.set('isEditing', true);
},
save: function() {
// In a real app, save to server
this.set('isEditing', false);
},
cancel: function() {
// In a real app, rollback changes
this.set('isEditing', false);
}
});
Create Templates
<!-- Posts list template -->
<script type="text/x-handlebars" data-template-name="posts">
<h1>Blog Posts</h1>
<ul class="posts">
{{#each post in controller}}
<li>
<h2><a {{action "showPost" post href=true}}>{{post.title}}</a></h2>
<div class="meta">
By {{post.author}} on {{post.publishedAt}}
</div>
<p>{{post.excerpt}}</p>
</li>
{{/each}}
</ul>
</script>
<!-- Single post template -->
<script type="text/x-handlebars" data-template-name="post">
{{#if isEditing}}
<div class="editor">
<h1>Edit Post</h1>
<p>
<label>Title:</label>
{{view Ember.TextField valueBinding="title"}}
</p>
<p>
<label>Body:</label>
{{view Ember.TextArea valueBinding="body"}}
</p>
<button {{action "save"}}>Save</button>
<button {{action "cancel"}}>Cancel</button>
</div>
{{else}}
<article class="post">
<h1>{{title}}</h1>
<div class="meta">
By {{author}} on {{publishedAt}}
</div>
<div class="body">
{{body}}
</div>
<button {{action "edit"}}>Edit</button>
<a {{action "showPosts" href=true}}>Back to Posts</a>
</article>
{{/if}}
</script>
Configure the Router
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
redirectsTo: 'posts'
}),
posts: Ember.Route.extend({
route: '/posts',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('posts', App.posts);
},
showPost: Ember.Route.transitionTo('post')
}),
post: Ember.Route.extend({
route: '/posts/:post_id',
connectOutlets: function(router, post) {
router.get('applicationController').connectOutlet('post', post);
},
showPosts: Ember.Route.transitionTo('posts')
})
})
});
App.initialize();
This complete application demonstrates:
- Routing: URLs map to application states
- Data binding: Templates automatically update when data changes
- Computed properties:
excerptandisPublishedderive from other properties - Controllers: Separate display logic from models
- Actions: User interactions trigger controller methods
- State management: Router handles transitions between states
Data Binding in Depth
Data binding is Ember's killer feature. Let's explore how it works and why it's so powerful.
One-Way Bindings
The simplest binding is displaying a property in a template:
<h1>{{title}}</h1>
When title changes, the heading updates automatically. You don't write any code to make this happen—Ember handles it.
Two-Way Bindings
Form inputs need two-way binding: changes in the model update the input, and changes in the input update the model:
{{view Ember.TextField valueBinding="title"}}
This creates an input that stays synchronized with the title property. Type in the input, and title updates. Change title programmatically, and the input updates.
Binding Between Objects
You can bind properties between any two Ember objects:
App.userController = Ember.Object.create({
name: 'John Doe'
});
App.greetingView = Ember.View.create({
userNameBinding: 'App.userController.name',
greetingText: function() {
return 'Hello, ' + this.get('userName');
}.property('userName')
});
When App.userController.name changes, greetingView.userName updates automatically, which triggers a recalculation of greetingText.
Binding to Paths
Bindings can traverse object graphs:
<p>Author: {{post.author.name}}</p>
<p>Email: {{post.author.email}}</p>
If post, author, or any of their properties change, the template updates.
The Power of Automatic Updates
The real power of bindings emerges in complex UIs. Imagine a dashboard with multiple views showing the same data in different ways: a table, a chart, summary statistics. Without bindings, you'd write code in every place the data might change to update every view.
With Ember's bindings, you simply bind each view to the data. Update the data once, and all views update automatically. This eliminates entire categories of synchronization bugs and makes the code dramatically simpler.
Computed Properties
Computed properties are functions that look like properties. They're one of Ember's most elegant features.
Basic Computed Properties
App.Person = Ember.Object.extend({
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
var person = App.Person.create({
firstName: 'John',
lastName: 'Doe'
});
person.get('fullName'); // "John Doe"
The .property('firstName', 'lastName') declaration tells Ember that fullName depends on these properties. Ember caches the result and only recalculates when dependencies change.
Chaining Computed Properties
Computed properties can depend on other computed properties:
App.Rectangle = Ember.Object.extend({
width: null,
height: null,
area: function() {
return this.get('width') * this.get('height');
}.property('width', 'height'),
isSquare: function() {
return this.get('width') === this.get('height');
}.property('width', 'height'),
description: function() {
var shape = this.get('isSquare') ? 'square' : 'rectangle';
return 'A ' + shape + ' with area ' + this.get('area');
}.property('isSquare', 'area')
});
Ember tracks the entire dependency chain and updates everything when needed.
Computed Properties with Arrays
Ember provides special helpers for computed properties that depend on array contents:
App.TodoList = Ember.Object.extend({
todos: null,
completedTodos: function() {
return this.get('todos').filterProperty('isCompleted', true);
}.property('[email protected]'),
remainingCount: function() {
return this.get('todos').filterProperty('isCompleted', false).length;
}.property('[email protected]')
});
The @each keyword tells Ember to watch all items in the array. When any item's isCompleted property changes, the computed properties recalculate.
Settable Computed Properties
Computed properties can be writable:
App.Person = Ember.Object.extend({
firstName: null,
lastName: null,
fullName: function(key, value) {
if (arguments.length > 1) {
// Setter
var parts = value.split(' ');
this.set('firstName', parts[0]);
this.set('lastName', parts[1]);
return value;
} else {
// Getter
return this.get('firstName') + ' ' + this.get('lastName');
}
}.property('firstName', 'lastName')
});
var person = App.Person.create();
person.set('fullName', 'John Doe');
person.get('firstName'); // "John"
person.get('lastName'); // "Doe"
This pattern is useful for properties that represent combined or transformed values.
Comparing Ember to Backbone
Since I've also been working with Backbone.js, I often get asked how Ember compares. They're quite different frameworks with different philosophies.
Philosophy and Approach
Backbone is minimalist. It provides basic abstractions—Models, Collections, Views, Routers—but doesn't prescribe how to use them. You make most architectural decisions yourself. This flexibility is powerful but requires discipline.
Ember is opinionated. It provides a complete framework with strong conventions. If you follow the conventions, many decisions are made for you. This can feel restrictive initially but provides consistency and reduces decision fatigue.
Data Binding
Backbone has no automatic data binding. You manually listen to model events and update views:
// Backbone
this.model.on('change', this.render, this);
Ember binds data automatically. Templates update when data changes without manual event handlers:
<!-- Ember: automatically updates -->
<h1>{{title}}</h1>
Computed Properties
Backbone models are plain JavaScript. You define methods, but they're not reactive:
// Backbone
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}
Ember computed properties automatically recalculate when dependencies change:
// Ember
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
Size and Dependencies
Backbone is tiny: about 6KB minified and gzipped. It depends on Underscore.js and jQuery/Zepto.
Ember is larger: around 40KB minified and gzipped. It depends on jQuery and includes Handlebars for templating. The larger size buys you more built-in functionality.
Learning Curve
Backbone is straightforward to learn. Understanding JavaScript and the basic MVC pattern gets you started quickly.
Ember has a steeper learning curve. The framework has more concepts to learn: bindings, computed properties, observers, the run loop, the router state machine. The investment pays off for complex applications.
When to Choose Each
Choose Backbone when:
- You want minimal framework overhead
- You prefer making architectural decisions yourself
- You're integrating with existing code gradually
- Your team values flexibility over consistency
Choose Ember when:
- You're building a complex, ambitious application
- You want strong conventions and consistency
- Automatic data binding and computed properties appeal to you
- You're willing to invest in learning the framework
Both are excellent frameworks. The right choice depends on your project's needs and your team's preferences.
Working with APIs
Ember works well with RESTful APIs, though the data layer is still evolving. Currently, you typically write custom code to load and save data:
App.Post.reopenClass({
find: function(id) {
return $.getJSON('/api/posts/' + id).then(function(data) {
return App.Post.create(data);
});
},
findAll: function() {
return $.getJSON('/api/posts').then(function(data) {
return data.posts.map(function(item) {
return App.Post.create(item);
});
});
}
});
App.Post.reopen({
save: function() {
var data = this.getProperties('title', 'body', 'author');
return $.ajax({
url: '/api/posts/' + this.get('id'),
type: 'PUT',
data: data
});
}
});
The Ember team is working on a data persistence library (Ember Data) that will provide Backbone-style fetch() and save() methods with automatic URL generation and JSON serialization. When it's released, working with APIs will become much simpler.
For now, wrapping your API calls in model class methods works well and keeps the loading logic centralized.
Performance Considerations
Ember's automatic updates are powerful but come with overhead. Here are some tips for keeping applications fast:
Use Ember.run.debounce for Expensive Operations
If an operation is expensive (like server requests), debounce it:
App.SearchController = Ember.Controller.extend({
query: null,
queryDidChange: function() {
Ember.run.debounce(this, this.performSearch, 300);
}.observes('query'),
performSearch: function() {
// Expensive search operation
}
});
This prevents the search from running on every keystroke.
Be Careful with @each
The @each property observer watches every item in an array. For large arrays, this can be expensive:
// This watches all items and all their properties
completedTodos: function() {
return this.get('todos').filterProperty('isCompleted', true);
}.property('[email protected]')
Only use @each when necessary. If you just need to know when the array changes (items added/removed), use todos.[] instead of todos.@each.
Minimize Observers
Observers run synchronously by default. Too many observers can slow down property updates. Keep observers simple and consider using computed properties instead when possible.
Use CollectionView for Large Lists
For rendering hundreds of items, use Ember.CollectionView instead of {{#each}}. It provides optimizations for large collections.
Best Practices
Follow Naming Conventions
Ember's naming conventions aren't just suggestions—they're how Ember connects pieces of your application. A PostController works with a Post model and looks for a post template. Follow the conventions and things work automatically.
Keep Logic in Controllers, Not Templates
Templates should be simple. Put display logic in controllers:
// Good: Logic in controller
App.PostController = Ember.ObjectController.extend({
authorName: function() {
return this.get('author.firstName') + ' ' + this.get('author.lastName');
}.property('author.firstName', 'author.lastName')
});
<!-- Good: Simple template -->
<p>By {{authorName}}</p>
Avoid complex logic in templates:
<!-- Bad: Logic in template -->
<p>By {{author.firstName}} {{author.lastName}}</p>
Use Computed Properties Instead of Observers
When a value depends on other values, use computed properties:
// Good: Computed property
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
Avoid observers for derived values:
// Bad: Observer for derived value
nameChanged: function() {
this.set('fullName', this.get('firstName') + ' ' + this.get('lastName'));
}.observes('firstName', 'lastName')
Computed properties are cached and only recalculate when needed. Observers run every time, even if you never access the result.
Return this from render
Make view render methods return this for chainable calls:
render: function() {
this.$().html(this.template());
return this;
}
Namespace Your Application
Use a namespace for your application to avoid global pollution:
window.MyApp = Ember.Application.create();
MyApp.Post = Ember.Object.extend({ ... });
Testing Ember Applications
Ember's structure makes testing straightforward. Models, controllers, and views are regular JavaScript objects that can be instantiated and tested in isolation.
Testing Models
module('Post Model');
test('isPublished returns true when publishedAt is set', function() {
var post = App.Post.create({
publishedAt: new Date()
});
equal(post.get('isPublished'), true);
});
test('excerpt returns first 200 characters', function() {
var longBody = new Array(300).join('a');
var post = App.Post.create({
body: longBody
});
equal(post.get('excerpt').length, 200);
});
Testing Computed Properties
test('fullName combines firstName and lastName', function() {
var person = App.Person.create({
firstName: 'John',
lastName: 'Doe'
});
equal(person.get('fullName'), 'John Doe');
});
test('fullName updates when firstName changes', function() {
var person = App.Person.create({
firstName: 'John',
lastName: 'Doe'
});
person.set('firstName', 'Jane');
equal(person.get('fullName'), 'Jane Doe');
});
Testing Controllers
test('edit action sets isEditing to true', function() {
var controller = App.PostController.create({
content: App.Post.create()
});
controller.edit();
equal(controller.get('isEditing'), true);
});
Testing frameworks like QUnit, Jasmine, or Mocha work well with Ember. The key is understanding Ember's run loop—wrap asynchronous operations in Ember.run() to ensure bindings and observers execute:
test('async operation', function() {
var person = App.Person.create();
Ember.run(function() {
person.set('firstName', 'John');
});
equal(person.get('firstName'), 'John');
});
Learning Resources
Ember is evolving rapidly, and the ecosystem is growing. Here are helpful resources for learning more:
Official Resources:
- emberjs.com: Official website with guides and API documentation
- Ember.js Guides: Step-by-step tutorials covering core concepts
- API Documentation: Complete reference for all Ember classes and methods
Community:
- Ember Discussion Forum: Active community discussion at discuss.emberjs.com
- IRC Channel: #emberjs on Freenode for real-time help
- GitHub Repository: github.com/emberjs/ember.js for source code and issues
Learning Materials:
- PeepCode Ember.js screencast: Video tutorial covering the basics (paid)
- Ember Watch: News and resources at emberwatch.com
- Trek Glowacki's Blog: In-depth articles about Ember internals
Example Applications:
- Study the source code of open-source Ember applications
- The Ember website itself is built with Ember
- Look for "Built with Ember" showcases online
The framework is pre-1.0, so APIs are still changing. Following the official blog and GitHub repository helps you stay current with changes.
The Road Ahead
Ember.js is ambitious in scope and vision. The team is working on several exciting features:
Ember Data: A data persistence library that will make working with APIs much simpler, similar to how ActiveRecord works in Rails. It will handle fetching, caching, and saving records with minimal configuration.
Improved Router: The router is being refined to make state management even more powerful and easier to use.
Better Documentation: As the framework matures, documentation is improving with more examples and clearer explanations.
Faster Rendering: Optimizations to make Ember even faster, especially for rendering large lists and complex UIs.
The framework is still pre-1.0, which means APIs can change. The team is working toward stability, but be prepared for some breaking changes if you adopt Ember now. That said, the core concepts are solid, and the framework is already being used in production by several companies.
Getting Started
Ready to try Ember? Here's how to get started:
- Download Ember: Get the latest version from emberjs.com. You'll also need jQuery and Handlebars.
- Read the Guides: Work through the official guides at emberjs.com/guides. They cover installation, basic concepts, and building your first application.
- Build Something Small: Start with a simple app—a todo list, blog, or contact manager. Keep it simple to focus on learning the framework.
- Study Examples: Look at example applications and see how they're structured. The patterns will start to make sense as you see them used in context.
- Join the Community: The Ember community is friendly and helpful. Don't hesitate to ask questions on the forum or IRC.
- Read the Source: Ember's source code is well-written and annotated. Reading it will deepen your understanding of how the framework works.
Ember has a learning curve, but the investment pays off. The automatic data binding, computed properties, and strong conventions make building complex applications more manageable. You spend less time writing boilerplate and more time building features.
The framework's opinionated nature means you'll do things "the Ember way," which might feel constraining at first. But as your application grows, you'll appreciate the consistency and conventions that keep everything organized.
If you're building an ambitious web application—something complex with rich interactions and substantial state management—Ember.js is worth serious consideration. It provides the tools and structure to build applications that scale without collapsing under their own complexity.
Give it a try. Build something small, work through the initial learning curve, and see if Ember's approach resonates with you. I think you might find, as I have, that Ember makes JavaScript application development more productive and more enjoyable.