Ember without Ember Data

Ember Data is a persistence layer for Ember.Js. Unlike Ember, which currently has a candidate for a 1.0 release, Ember Data is still very much a work in progress. This has been a source of confusion for people who are learning Ember, as the two frameworks are complimentary but currently exist in different realms of stability.

Ember Data has ambitious goals, and it has come a long way in the last year. If you’re the kind of programmer who loves working on upcoming stuff, you might find it exhilarating. On the other hand, it is completely understandable if you’d want to avoid it. Deprecations and changing APIs can be frustrating and time consuming.

One thing that is not always clear to people starting with Ember is that Ember works perfectly well without Ember Data! Trust me on this: Discourse doesn’t use Ember Data for persistence and it’s working quite well. Moreover, using AJAX with Ember is something that is not difficult to do.

Ember Models that are just Objects

Ember includes an object model that most people with an OOP background should find familiar. A subclass of Ember.Object works very well for a data model.

Here’s what a class might look like to represent a link from reddit:

App.RedditLink = Ember.Object.extend({});

You could then easily instantiate it and use getters and setters to access its properties:

var link = App.RedditLink.create();
link.set('url', 'http://eviltrout.com');
console.log(link.get('url')); // http://eviltrout.com

If you like, when you construct your model instance, you can pass it a regular Javascript object with the properties rather than setting them one at a time:

var discourseLink = App.RedditLink.create({
 title: "Discourse",
 url: "http://www.discourse.org"
});
console.log(discourseLink.get('title')); // Discourse

Here’s how you’d bind those properties to a handlebars template:

Title: {{title}}
Url: {{url}}

Once bound to a template like this, if you called set on your model, it would automatically update the HTML.

Accessing Reddit via JSONP

Data models are a lot more exciting when you fill them real data. Let’s write a method that finds the links from a subreddit. Reddit provides a JSONP API that we can access via jQuery:

$.getJSON("http://www.reddit.com/r/" + subreddit + "/.json?jsonp=?", function(response) {
 // response contains the JSON result
});

The response from reddit’s API includes the colleciton of links under data.children, but their properties are under an additional data attribute. We can loop through them like so, creating instances of RedditLink as we go:

var links = Em.A();
response.data.children.forEach(function (child) {
 links.pushObject(App.RedditLink.create(child.data));
});
// links now contains all our `RedditLink` objects!

$.getJSON is an asynchronous call. It follows that our model’s finder method will have to be asynchronous as well. One common approach to dealing with this is to pass a callback function to our finder method. When the $.getJSON call finishes, it can execute the callback with the result. What happens, though, when you need to handle the errors? You’d have to supply two callbacks: one for the error callback and one for the success callback.

Promises

This is all much cleaner to do with Promises. Promises are objects you return from your functions. They contain a then method that you can call when the operation is complete.

The nice thing about this is you don’t have to supply your callbacks to your function - you just attach them to the Promise object that your function returns. It ends up being a lot cleaner and simpler to follow. Additionally, Promises can be chained, so that the result of one promise is only passed through to the next function in the chain once it is complete.

jQuery conveniently return promises from all its AJAX calls, so we can just make use of it. Here’s how our finder looks, returning a promise:

App.RedditLink.reopenClass({
 findAll: function(subreddit) {
 return $.getJSON("http://www.reddit.com/r/" + subreddit + "/.json?jsonp=?").then(
 function(response) {
 return response.data.children.map(function (child) {
 return App.RedditLink.create(child.data);
 });
 }
 );
 }
});

Notice that we’re returning the result of $.getJSON, but also calling then on it. This means that the Promise that our findAll method returns will eventually resolve to our list of RedditLink objects. Here’s how you could you could call it and log the results from the subreddit /r/aww:

App.RedditLink.findAll('aww').then(function (links) {
 console.log(links); // logs the array of links after it loads
});

Putting it all together

I’ve created a github project that puts all the code from this blog entry together. You can also try it in your browser.

The code for the application is quite short, which I think reflects Ember’s greatest strength: as a developer you have to write less code to get stuff done.

I implore you to not be scared off by Ember Data’s current state. Ember itself is quite stable, and it’s easy to get started with AJAX calls like this today.

Update - Jun 30 / 2013

I’ve updated the code to use Ember 1.0 RC6 and added a bunch more comments. The code is not exactly the same as above, but it has a bunch of new features and is still based on the same principles. Download it and try it out!


Imported from: http://eviltrout.com/2013/03/23/ember-without-data.html

Nice article! This should help a lot of people build their models until Ember Data matures. Can you share a findAll example that uses Ember.Deferred and a pub/sub callback to populate the model? I'm using Faye to return a list of models but I can't find an idiomatic example of returning a promise from a find/findAll function that doesn't use jQuery's deferred implementation.

When you say "if you called set on your model, it would automatically update the HTML" do you mean like web sockets?

This is just referring to ember's bindings with views. That if you update a models attribute the view will also update. This is just client side.

Got it. Thought it was also referring to syncing with a server.

Update: I managed to prototype a simple model using Faye and Ember.Deferred after looking at packages/ember-runtime/lib/mixins/deferred.js. Fortunately returning Ember.Deferred promises from the model seem to work the same as the promises returned from $.ajax().

I am using https://github.com/cerebris/em..., a tiny wrapper using $.ajax.

Great article. Thanks. I've been searching for a long time for a non ember-native data object approach and at the same time a proper integration with jquery's AJAX promises. It's great I found 'em both in here :)

I'm having trouble groking why we have to reopenClass? Shouldn't I be able to add the find and findAll methods to the Ember Object when I call App.NameOfObject = Ember.Object.extend({ find: function(){ //... } })? I attempted this and Ember could not find the method find. This seems very hacky to me! I think that this is an inconvenience we will all obviously have to face while the core team figures out Ember-data and gets it into Core. Until Ember-data is stable, we will have this relatively ugly workaround? Additionally, thanks for this article! I appreciate it very much

Anyone interested in using Ember.js without Ember Data should check out my small and fast model library, ember-model: https://github.com/ebryn/ember...

Awesome article. Thanks a lot Robin! I don't live in Canada but I really wanted to attend Embergarten. Can we have a webinar for this?

It looks promising, but why hardcode '.json'? There are APIs don't like .json at the end of the url. Also is there a way to adapt different json structure? Eg. django rest framework doesn't natively return json structure that ember expects.

The RESTAdapter can be easily extended. To remove the .json suffix, just override buildURL. There are also some other configuration options on the RESTAdapter.