Should Components Load Data?

Those of us who care about architectural best practices have been battling with this question.

In our last post about component UI state communication we reviewed different approaches for UI state flowing across components. We didn’t say much about data though.

There is strong consensus that components should not be responsible for fetching data. For instance as this Ember Data document suggests:

“In general, looking up models directly in a component is an anti-pattern, and you should prefer to pass in any model you need in the template that included the component.”

The recommended pattern being:

“Components are isolated entities that consume data through their interface, react to data changes that flow via data binding, and possibly send up named actions”.

{{awesome-data-grid-component data=model.items}}

The answer to our question seems to be a resounding no. This design pattern almost forbids accessing data directly from a component (by means of passing in or injecting a reference to the store).

Data-loading components?

But there are non-obvious use cases which can benefit from abstracting responsibilities like loading data.

{{awesome-twitter-component tweets=tweets}}

Loading tweets does not feel like an appropriate concern for the router!

There are valid arguments for encapsulating data retrieval:

{{awesome-twitter-component handle='emberjs'}}

A different case

Imagine we have a template rendering a blog post model:

export default DS.Model.extend({
  title: DS.attr('string'),
  body: DS.attr('string'),
  comments: DS.hasMany('comment', { async: true })
});

Its comments can be passed into a comments-component, abiding by standard practice.

{{comments-component comments=post.comments}}

But we also need to display ads (Ad is also a model in our application).

{{ad-component keywords=post.body }}

This component requires text input to feed its internal algorithm of ad display. It makes total sense for it to access ad data in the Ember Data store.

export default Ember.Component.extend({
  store: Ember.inject.service(),

  displayAds: function() {
    const adTags = algorithm({ text: this.get('keywords') });
    this.get('store').query('ads', { tags: adTags }).then(function() {
      ...
    });
  }

});

Whether a component requires non-store data like the Twitter component or store data like our ad component, there is sound sense to both. We must note that neither change the URL.

Isn’t there a standard answer to our question? So far, it looks like “it depends”.

Asynchronous Components

Routes very soon will be able to directly render components, bypassing and making controllers and views obsolete. This feature is called “routable components”.

An Ember document about this feature introduces the notion of data-loading components called “asynchronous components”:

A canonical example of a component that needs this capability is a file hierarchy browser. Each time the user clicks a node to expand it, we may need to retrieve additional data, without involving a route transition.

That’s very similar to our ads component!

The document goes on:

Once we have asynchronous components, it may be tempting to handle all data loading through them, eliminating traditional Routes, and essentially moving the model hook onto Components. But we think there are still strong architectural reasons for having Routes as a separate concept. Routes orchestrate transitions, and often have important work to do before you even know what components need to be on the page.

Components today have a broader meaning. There is no requirement for a component to be linked to either a template or data service.

Some components will necessarily be application-specific. Others will be reusable outside of the application context.

Some will be passed in data. Others will load data by themselves. And some of those might be entirely data-centric, with no UI whatsoever.

To our question “Should components load data?”, the document concludes:

If you’re also changing the URL, use a Route.

If you can’t decide exactly where you’re going until after looking at the (asynchronously-loaded) data, use a Route.

Otherwise it’s probably fine to use an asynchronous Component.

Enjoyed this article? Don't miss my next one!

Leave me your e-mail for content that will help you master Ember:

Do you want to master Ember fast?

Leave me your e-mail for helpful updates delivered straight to your inbox.

(A few e-mails per month. No BS. Unsubscribe anytime!)