I have an action that causes the app to hang several seconds. I want to add some sort of loading indicator to show that it's doing something, and not just frozen.
I've already tried a couple of things, some of which were complete experiments that I didn't think would work.
First of all, here is what I had before I tried adding any sort of indicator:
filterObserver: function(){
// we needed the debounce because it would crash the app if it took too long
// it has to filter through ~10,000 records
Ember.run.debounce(this, this.filterFoo, 1500);
}.observes('filterValue'),
I thought this would work, but it seems it waits until everything in the observer is finished before it rerenders the page:
controller.js
isLoading: false,
filterObserver: function(){
this.set('isLoading', true);
Ember.run.debounce(this, this.filterFoo, 1500);
this.set('isLoading', false);
}.observes('filterValue'),
template.hbs
<ul class="project-list {{if isLoading 'loading'}}">
{{#each group in foo}}
{{group-item group=group}}
{{/each}}
</ul>
So, I thought maybe I needed to force it to rerender in order to show the changes. I moved the entire list to a component in order to have access to the component's rerender
method:
component.js
export default Ember.Component.extend({
loadingObserver: function() {
this.rerender();
Ember.run.schedule('afterRender', this, function() {
this.sendAction('filterAll');
});
}.observes('isLoading')
});
controller.js
actions: {
filterAll: function() {
Ember.run.debounce(this, this.filterActivities, 1500);
this.set('isLoading', false);
}
}
So, I thought maybe Ember's run loop would work. By this point, I was pretty frustrated, but I wanted to try everything that could maybe work:
component.js
export default Ember.Component.extend({
loadingObserver: function() {
this.rerender();
Ember.run.schedule('afterRender', this, function() {
this.sendAction('filterAll');
});
}.observes('isLoading')
});
None of those worked.
I'm aware of the different route methods like afterModel
, destroy
, etc. My page has already loaded by this point, so none of those work in this case.
I believe this is because Ember will wait until everything in the observer is completed before it rerenders the template. So, I need some way for it to either show this indicator whenever anything changes in the template, or wait for it to finish setting the variable and adding the class before it continues executing the action's code.
Thoughts? Ideas?
For the record, I know that 1.13 introduced glimmer, which would help with the app hanging. However, we rely on a couple of addons that still use 1.11, so I'm afraid that we're stuck with it for the time being.
Edit
Apparently run.later
only works in my case because I also use run.debounce
. The code below doesn't work in my particular case because run.next
thought that the run.debounce
code was finished when it wasn't. This should work in the majority of cases:
Ember.run.once(this, function() {
this.set('isLoading', true);
});
Ember.run.next(this, function() {
// your code here
// `isLoading` will be true while this is running,
// so your loading indicator will still be present
this.set('isLoading', false);
});
My original answer is still valid if you need to have an indicator with something that runs outside Ember's run loop. However, you will probably be able to use the code above the vast majority of the time.
Original Answer
I was actually able to solve this. I simply used Ember.run.later
in order to have Ember wait until all other async events ended. In my app, this simply amounts to:
var _this = this;
this.set('isLoading', true);
Ember.run.debounce(this, this.filterFoo, 1500);
Ember.run.later(function() {
_this.set('isLoading', false);
}, 1500); // I set the timeout to 1500 ms because that's the same as the debounce
I found this buried in Ember's guides: A SPINNING BUTTON FOR ASYNCHRONOUS ACTIONS. You can read more about run.later
in Ember's docs.
If I didn't do that, it'd try to execute all of the code at once. This basically just has it wait until it either finishes or times out.