Who should Request Data? component vs route


#1

Hi,

I was arguing with my college about the best way to handle data. In route (Ember tutorial approach) or in component

(The pros of component approach is cons of route approach, vice versa)

###Component handling data approach: ####Pros:

  • Component high portability
  • More isolation and focus on the task
  • Thin route ####Cons:
  • Fat components
  • Number of Components may increase

#2

I’m guessing you mean change data, not request data. If it’s request data, then it’s definitely route. Changing data, I go for DDAU so it’s also route (with help from this addon). But I believe there’s a lot of developers that don’t do this so I’m also curious on the opinions of the others.


#3

I used to subscribe to the “only request data in the route” philosophy, but I have more recently backed away from that for a few reasons.

Regarding the Pros & Cons @AbdullahD listed — I definitely agree with the Pros. Here’s my thoughts on the Cons:

Fat Components:

There’s an approach I’ve begun to embrace that I think helps to address this concern, and that is separating data components (which are generally tagless components) from UI components. Let’s say, for example, I wanted to build an autocomplete component, I would create two (or more) separate components — one for fetching and yielding the currently matching data & update actions and one that accepts a resolved list. The template for that might look something like:

{{#fetch-autocomplete-data as |fetch|}}
  {{autocomplete-input 
    input-changed=(action fetch.updateQuery)
    results=fetch.results
    action=(action 'doSomethingWithChosenResult')
  }}
{{/fetch-autocomplete-data}}

The nice thing about this pattern is that these two components are so much easier to test when these two behaviors are completely separate — and, both of these components become usable for other purposes than just an autocomplete widget. In fact, you could still use the autocomplete widget route-driven data because it only needs to know what the result set is and triggers an action for when it should expect new data.

Number of Components May Increase

This is a worthwhile concern as there is definitely some overhead that comes with the initialization of each component on a given page. But, it is my understanding that with Glimmer 2, some (or all?) of the overhead of building components gets moved into that lower, more performant, layer so the cost for each component instance goes way down.

In addition to the performance benefits from Glimmer 2, I have found that the flexibility and composability of more behavior-driven/isolated components has actually reduced the number of components I have needed overall because many of them are far more reusable.


#4

I’ve only worked with React a little bit, but it seems the React camp has embraced having more components with their own responsibilities as opposed to having large components. Some of these components fetch data and handle actions called “container components”, and other components which are more presentational which they’ve called “dumb components”.

Lately I’ve been favoring this technique as I’ve found it makes testing components much simpler. Sometimes I’ll use a separate “container component” or I’ll just having the data fetching and actions in my controller.


#5

I meant the whole data manipulation . The route just passes the params only.


#6

I always have concerned what if we created initializer and injected some global services. Does this approach slow ember if I had many components? or it doesn’t effect that much.

Gonna elaborate more: example: We have 10 global services (each serviceinit needs 10ms) and 50 components (each component init needs 10ms) ####Is this correct? (10 x 10) x (50 x 10)

Or ember have some optimizations methods?


#7

That’s how I got the idea in the first place. In ember, we will have data component = container components UI component = dumb components


#8

That’s a question that I’ve asked a few weeks ago and from what I can gather, no, it doesn’t affect it that much. According to the docs, services are lazy loaded:

Injected properties are lazy loaded; meaning the service will not be instantiated until the property is explicitly called. Therefore you need to access services in your component using the get function otherwise you might get an undefined.

Also, I haven’t tested by but I’m pretty sure services are initialized once for all components, routes, etc. that depends on it. So 10 services = 10ms not 100ms.


#9

I think it’s a matter of context. For my apps, if we’re talking about a combo box that’s loading lookup data from the back-end that rarely if ever changes and is not context sensitive (that is to say, the data doesn’t depend on where I am in the app, what the state of other models might be, etc.) then I build the data into the component. Otherwise you end up with very messy model hooks where you’re having to do ugly RSVP.hash calls to load all of the lookup data needed by UI components…that just seems plain wrong to me. A good example of this is a drop-down with US states read from the backend…not likely to change often and likely no routes where Alaska doesn’t exist as a valid state…

If the data is context sensitive (which it turns out to be more times than you’d anticipate), then it should be handled by the route. I can twist my previous example very quickly in this case into one where maybe it’s a shipping page and we don’t ship to Alaska…now if I’ve built a component that queries it’s own US states I have to either create a new one for this context or muddy up the existing component and leak route specific context information into it. In a case like this, the route determines the context and should be the one supplying the data to the component.


#10

@dgeb gave a great talk at EmberConf 2015 on this topic. My thoughts below follow his ideas.

Let’s say you’re building a blog post page. You can’t show the page without the blog post itself. And you might need some information about the author. So those fetches I would put in the route. They’re critical for the page. If they fail, the page fails.

But the comments and a “related articles” widget might be nice to haves for the page. In that case, I would defer fetching them to the components.

Those components might have something like the following:

article: Ember.computed({
  get() { return undefined; },
  set(_, article) {
    this.set('isLoading', true);
    if (article == null) return null;

    article.get('comments').then((comments) => {
      this.setProperties('isLoading', false);
    }).catch(() => { this.set('errorLoading', true); });

    return article;
  }
})

and a template that knows about isLoading and errorLoading.


#11

I agree. Those nice to haves for the page should be lazily loaded in the components. What’s neat though is you get that for free in model relationships. You can have the post model have a hasMany relationship with comments. The comments will not be loaded until requested through the template.hbs or component.js.

Also, every model record has an isLoading property. So you could use that instead of manually setting it up in your component.

TL;DR

Depending on what you’re trying to do, your example above can easily be implemented in template.hbs by utilizing model relationships.

{{#each post.comments as |comment|}}
  {{#if comment.isLoading}}
    <!-- Show spinner or whatever here -->
  {{else}}
    <!-- Show the comment here -->
  {{/if}}
{{/each}}

#12

Worthwhile reading: Actions Up, Data Sideways.

Blindly following the rallying cry of DDAU, I’ve definitely suffered the article’s coupled and ever growing routes and components. It generally agrees with this thread. Sometimes the request is okay in the route. Sometimes it’s okay in the component.