Achieving Immediate Feedback in Map-based Web Applications
By: Matt Gardner
Tags: Concurrency, Maps, Nyc Planning Labs, Ember

Network-related delayed reactions can confuse users

When an app blossoms from a prototype into a product, it begins to run up against some unique edge-cases. In the case of Planning Labs’ just-released ZoLa, we were eager to tackle something that had been bothering us for some time now: delayed feedback after map clicks (seen above).

This was all expected behavior — in Ember, the template doesn’t render until the model hook resolves in the route. This led to small delays after clicks, which might confuse users and give the impression that something is wrong. We needed a better way to handle network load time by giving instant feedback to the user.

In comes the excellent Ember Concurrency, a powerful addon for managing asynchrony. I won’t repeat Vincent Bello’s excellent explanation for how to use this addon in routes, but in short, we make the model hook in our routes return a Promise-compatible task which gives us much more control over multiple subsequent route transitions.

Without concurrency, multiple clicks across the map would initiate several route transitions, but only the last-resolving network request would have its template rendered. This wouldn’t work for us — users who weren’t seeing immediate feedback were likely to do this anyway (because something seemed wrong), leading to multiple clicks and more confusion.

With concurrency, our routes return cancellable tasks. Hence, the user’s last-clicked geometry was the only one to resolve after the network request completed, even if they sporadically clicked fifteen times on the map. Our app would always reliably transition the route to the last-clicked route, not the last-resolved network request.

That’s great and all, but Vincent Bello’s post above does introduce some necessary cruft into our routes. We do this by tricking Ember into believing that the model has resolved. This is accomplished by returning a plain object that wraps the Ember Concurrency task. However, Ilya Radchenko’s clever ember-data-tasks addon configures Ember’s data store to return tasks by default. We can write this instead, removing additional cruft:

Two Caveats

While this gives us more control over asynchronous behavior, it changes some things about the way one might normally use Ember. The first is link-to : we can no longer pass a full model object as an argument to the link-to component because our task-aware templates expect it to be wrapped in a plain object. This means we must pass IDs to the link-to component.

The second is that afterModel , much like the model in our templates, won’t behave as something that occurs after the model has resolved. To Ember, the plain object isn’t a promise and has resolved just fine. Although the returned task is then -able, we run up against “race conditions” in which afterModel behavior occurs out-of-order with the user’s behavior. Hence, for any afterModel route logic, we use tasks:

After all of that, our users now receive immediate feedback after any clicks:

This solves our main issue with map clicks. Although the data is still being fetched, we provide instant feedback with the data we do have, and then hydrate the view with data when the network request resolves.

If you’re interested, check out the source code on this branch, and don’t forget to check out ZoLa, NYC City Planning’s zoning and land use map!