What's a good pattern for fetching and rendering ancillary data?


#1

Let’s say you’re building a Twitter client for the web. You have a pretty standard setup:

App.Router.map(function() {
  this.resource('tweets', { path: '/tweets/:id' });
});

The TweetsIndexRoute fetches a tweet by ID and connects it to the controller and, in turn, the view. All is well.

Then your product designers come up with a new feature: “suggested follow.” When you’re looking at a tweet, the app shows a list of people you might want to follow in the sidebar.

The most obvious implementation works:

App.TweetsIndexRoute = Em.Route.extend({
  model: function(params) {
    var tweet = App.Tweet.fetch(params.id),
        suggestions = App.Suggestions.fetch(params.id);
    return RSVP.all(tweet, suggestions);
  }
});

The route makes the two fetches in parallel and then blocks on both. Everything works well.

Then your users start complaining. The /api/suggestions/292 requests take a long time, and they’re blocking the users from seeing the main content they want to see. So you decide to fetch them later:

App.TweetsIndexRoute = Em.Route.extend({
  model: function(params) {
    return App.Tweet.find(params.id);
  },

  setupController: function(controller, tweet) {
    this._super(controller, tweet);
    controller.set('suggested', App.Suggested.find(tweet.get('id'));
  }
});

But now your app starts spitting out error messages about the sidebar view re-rendering while it’s in the inBuffer state (since the AJAX call can come back any time). To get around that, you delay the fetch until after the first render:

setupController: function(controller, tweet) {
  this._super(controller, tweet);
  Em.run.scheduleOnce('afterRender', function() {
    controller.set('suggested', App.Suggested.find(tweet.get('id'));
  });
}

Is that the best practice? Is there somewhere else these ancillary models should be fetched and connected?


#2

Assuming App.Suggested.find returns a promise you could also do:

App.Suggested.find(tweet.get("id")).then(Em.run.bind(function(suggested) {
  controller.set("suggested", suggested);
}));

Ideally you wouldn’t need the Em.run.bind if find handles executing in a run-loop for you.

Other than that, I pretty much do as you describe - essential stuff for the page goes in model or afterModel to display loading screens etc until it’s loaded, then the rest in setupController for ancillary calls.


#3

I have a newbie question that relates to this issue. In the RSVP example in the original post, how would you reference the tweet and suggestions models in the template? I can see the models are loaded in the ember debugger, but I can’t find the right permutation to actually access them. I’ve tried prefixing with “controller” and “content”, and just using the name of the class itself, but the template won’t render my data. If I just return a single object in the “model” function it works fine.


#4

If you’re setting the suggested property on the controller, then simply {{#each suggested}} should iterate over them.