Async or Swim: Replacing your Route models with Ember Concurrency Tasks

Avery Bloom
4 min readDec 23, 2017

The asynchronous nature of Ember.js apps can be a tremendous asset, but it’s always important to let the users know when something is loading so your app doesn’t feel completely broken. Ember has a wide variety of tools to handle this sort of thing, but using promises and displaying a loading state to the user is pretty wonky out of the box. Enter, Ember Concurrency. I’m not going to re-hash Ember concurrency’s greatness, just take a look over here, or over here. Long story short, use it and love it, and revel in the glory of async-iness.

That being said, the patterns to use Ember Concurrency effectively can take a moment to wrap your head around. Once you’ve fallen in deep deep love with EC, you’re going to want to use them everywhere. One of the core async elements of your Ember app is probably in your model() hooks.

So how how do you banish promises, and use all that Ember Concurrency goodness in your route’s model hooks?

We’ll use the classic myBlog example to get us going…

The Basic Case

Lets say you just need to make a basic call to Ember Data in your model hook to query for today’s blog post. Easy peasy. Just return an object out of your model that includes the object you want to use for your model (in this case “blog”), as well as the task. When the task is complete, the model.blog will be the result of a completed EC task, and you’re good to go.

Cool!

The Case of the Dynamic Segment

It’s pretty rare to just grab any ol’ thing from the back-end, usually your user is looking for something specific. So what if you have a dynamic segment in your route? Lets imagine someone wants to read a particular blog post of yours.

Just pass along the id from your model hook’s param argument to your task. In this case we’ll be passing blog_id

Lovely!

The Case of the Nested Route

Proof = pudding: Here’s an Ember Twiddle showing how it all comes together: Ember Concurrency With Model Hooks and Nested Routes

Ok so this is when it gets a little trickier. What do you do about nested routes? Frequently in a nested route, a child route needs the resolved data from the parent. For instance in the case of a blog/1234/comments route.

When you’re using the model() hook in Ember, one thing that’s nice is that you know that your parent model has finished resolving by the time you get to a child route’s model() hook. If you’re not using Ember Concurrency, you would probably return something like: modelFor('blog').get('comments') However, the downside is that your users need to wait for everything to load up and dealing with the loading sub-states for deeply nested routes can be really messy.

So, how do you combine the need to have a resolved parent model in order to load the correct child route’s data, with your groovy Ember Concurrency Task based models?

Yield modelFor() in your sub-route model task, and then act on that data to grab what you’re actually trying to load:

Tada! Down with the model hook… long live the model hook!

Bonus Points

Here are a few things that will upgrade your EC Model shenanigans from a solid yellow belt up to BLUE…

+2pts Pass loading and value separately

Making sure that all of your templates and components are dealing with your model in its task form can be overkill. If you want to pass around your blog in its resolved form (meaning null when unresolved otherwise the resolved value) then add the following computed properties to your controller. Also, adding a convenience computed for isRunning:

blog: reads('model.blog.value'), // this will be null, then a value
isLoading: reads('model.blog.isRunning')

+3pts Cancel abandoned model calls

What happens if your user doesn’t wait until your model is done loading? Let’s clean up after ourselves by canceling unfinished tasks:

Yep! Just add .cancelOn('deactivate') and you’re in the clear.

Gotchas #1

Directly returning a task in the model hook (not wrapped in a hash) will behave the same way as if you were using regular promises. That’s because the tasks are promise-like, and Ember assumes that if you return something promise-like from your model() hook, it should wait for it to resolve first.

// DONT:
model(){
return this.get('blogTask').perform();
}
// DO:
model(){
return {
blog: this.get('blogTask').perform()
};
}

--

--