Components that deal with async stuff


#1

Here is a stripped down example of a component which waits for some async stuff:

{{x-foo onSearch='findThings'}}

http://jsfiddle.net/amk221/da6bt0wL/

If the component happens to be destroyed, the promise which is searching for new results will still callback to the component, but the component will be dead by that time.

Perhaps this: {{x-foo results=results}} would solve the problem? But it is less nice.

What do you do?


#2

I would do something like this.

Wrap your results object into some sort of model called result-set for instance. On result-set would be a search function which takes a query and updates it’s isSaving isError values like ember data does, it also has a content value which contains the actual results. The component would sendAction(‘search’, query) and the router would call search on the result-set the data would go down to the components via data bindings for isError, isSaving, and content. The component template would reference isError, isSaving, and content to display the different states.


#3

Strictly following Actions Up Data Down does make like easier (generally), but in some circumstances it seems unreasonable

For example, if imagine lots of searchable dropdown boxes:

loadingTasks: false
loadingContacts: false
loadFiles: false
tasksError: null
contactsError: null
filesError: null
tasks: []
contacts: []
files: []
{{dropdown-box error=tasksError isLoading=loadingTasks results=tasks onSearch='findTasks'}}
{{dropdown-box error=contactsError isLoading=loadingContacts results=contacts onSearch='findContacts'}}
{{dropdown-box error=filesError isLoading=loadingFiles results=files onSearch='findFiles'}}

…fine, except that dropdown box is nested 2 levels deep. So 9 things have to be passed down through the layers which results in a lot of confusing code.


#4

You can bundle them together IE filesError and files and filesSaving would all be in the file object which you pass down. Then you can unbundle as you go to remove the need for name spacing things like errors.

For example

{{dropdown-box model=tasksSet search=onSearch}}  

{{dropdown-box model=contactsSet search=onSearch}}  

{{dropdown-box model=filesSet search=onSearch}}  

then inside the respective components you can do

{{#if model.isSaving}}
{{else if model.isError}}
{{else}}
 {{! loop through the model.content or whatever you want to call the actual results}}
{{/if}}

If it’s even deeper you can again bundle according to whatever makes sense for you. I used an architecture sort of like this in one of my ember apps and it turned out to be useful for error and saving handling since each component knew if it was saving/erring no matter where the save was called since the model held that data not the controller.

The thing I didn’t like and don’t yet have a good solution for is getting the actions to the top to be handled but I’ve been playing with the idea of using services to short circuit events to the top since the service is a singleton and I can use it to manage all data interactions. Not sure if it’s a good idea yet…


#5

thank you @varblob

Why would I make all that work for myself sending “data down”, through layers and layers when I could send the component up. e.g.

component/dropdown-box.js

search: function() {
  this.sendAction('onSearch', this, this.get('query'));
}

route/template.hbs

{{dropdown-box value='find me' onSearch='getResults'}}

route/application.js

actions: {
  getResults: function(component, query) {
    component.set('isLoading', true);
    this.store.findQuery('thing', { q: query })
      .then(function() { component.set('results', results); })
      .catch(function(e) { component.set('error', e); })
      .finally(function() { component.set('isLoading', false); });
  }
}

#6

To me it’s because that’s the architecture that ember seems to be supporting.

Ignoring the details of implementation I think the difference can be boiled down to observer style MVC vs controller heavy MVC. ie observer style the view observes the model and updates itself, controller heavy the controller serves as an intermediary for view model interactions.

I’ve tried both approaches across a few different languages (actionscript, C#, java). They both have advantages and disadvantages and ultimately I did end up liking the more controller heavy approach better.

BUT

In learning ember I’ve felt that the expectation of the architecture is to follow a more observer based approach. I’ve also gotten a similar sense from poking around discuss and other ember environments. In a very opinionated framework such as ember I’ve found it advantageous to adapt to the architecture rather than impose my personal preference.


#7

Thanks for the input :smile:

I think we got off topic. Neither solution fixes the problem of what to do when a promise resolves but the component is dead.

I guess a simple if (!this.get('isDestroyed')) will solve it for now


#8

Always fun to get into a little bit of architectural discussion now and again :smile: