Passing functions that return data into components


#1

Is there a way for the route to define data that could be loaded, but only if needed, on-demand? For example, perhaps the route could define functions that return promises that components could run if they needed the data?

So in the route:

export default Ember.Route.extend({
  fetchWidgets() {
    return this.store.findAll('widget');
  }
});

Then in the template:

{{! We don't need to load the data if the widgets aren't showing }}
{{#if showWidgets}}
  {{list-widgets widgetData=fetchWidgets}}
{{/if}}

Then in the component (as an arbitrary example, as I guess all of this is):

export default Ember.Component.extend({
  init() {
    this._super(...arguments);

    this.get('widgetData')().then((widgets) => {
      this.set('widgets', widgets);
    });
  }
});

I haven’t checked to see if this actually works but was playing with the idea/concept. The reason why I like this idea is that at least the definition of the API call is in the route, instead of putting it into the middle components after injecting the store service. Even though the API call is run by the component, at least the code for it is defined in the route.

Does this describe an anti-pattern or other Ember sin? Or is there some baked-in way to accomplish what I’m describing? I realize that this could be a total newbie question, and I’m OK with that. :slight_smile:


#2

I’ll talk to myself since no one else is responding. :slight_smile:

After experimenting a little, it looks like this won’t work exactly the way I proposed. However, a controller could provide an action that returns the promise for the widgetData.

The controller:

export default Ember.Controller.extend({
  actions: {
    fetchWidgets() {
      return this.store.findAll('widget');
    }
  }
});

The template:

{{#if showWidgets}}
  {{list-widgets widgetData=(action "fetchWidgets")}}
{{/if}}

The component:

export default Ember.Component.extend({
  init() {
    this._super(...arguments);

    this.get('widgetData')().then((widgets) => {
      this.set('widgets', widgets);
    });
  }
});

Still not sure how I feel about this, but I welcome discussion on why this would be a good or bad idea. I just like the idea of the components not being responsible for querying data from the API.


#3

I would do this with the route-action helper, have an action in the route that returns the widgets. Setting a property is fine but a DS.PromiseObject or DS.PromiseArray can be interesting if you plan to use that property directly in your component’s template.


#4

That sounds like it would be a great solution.

How would I use DS.PromiseArray in the route layer so that it’s only picked up and resolved by a component that’s initialized?


#5

I think you can use a (computed) property that returns a Promise. The request will be triggered only when the data is loaded. You can easily create a simple promise wrapper component that yields the result of the promise to the block, like so:

{{#if showWidgets}}
  {{#my-promise-resolver widgets as |result|}}
     {{ list-widgets widgets=result }}
  {{/my-promise-resolver}}
{{/if}}
widgets: computed(function () {
  return this.store.find....
}),

You can take a look at ember-deferred-content although for something simple like this one, I wouldn’t use an addon.


#6

Thanks for the tips. I have things kind of working right now, but it’s not reliable when the widgetData loader is being passed to multiple components in the same view template.

My example is simple, but my app will be doing this all over the place. Using something like ember-deferred-content would be very valuable, so I appreciate the pointer.