Ember.js Loading substate templates
Today, I will be talking about my experience and what I have learned by implement loading substates in Ember.js. It is a very convenient way of showing loading states for conventional uses, such as loading spinners or loading progress bars.
To get started, we have to understand our project file structure.
I used Ember CLI to create my project, but in general your project file structure should look something like what I have below (I’m excluding the files/folders that we don’t need, but for more information on what an Ember CLI project structure looks like, you can go to their guide). We only care about the templates folder in our exploration.
// project structure
.
.
.
app
|_ templates
|_ styles
.
.
.// templates folder
templates
|____ application.hbs
|____ routeName.hbs
or
|____ routeName
|__ routeName.hbs
|__ nestedRouteName.hbs
Hopefully you guys can visualize the project structure at this point. We are mainly going to focus on the the routes templates within the templates folder.
Also, before we completely dive in, you should read this Ember guide on loading substates and reference it whenever you get confused; it certainly helped me a lot when I was going through all of this myself. In addition, I would highly recommend reading Chris Ball’s guide on this topic before we get our hands dirty. You should also have prior knowledge of promises in JavaScript so that you can understand how to trigger the loading substate.
So, let’s say you wanted your website to show a loading state while you first load into it. This is a good idea because you would want to notify the user that your website isn’t frozen. For example, If I am trying to load Instagram and I see a blank white screen because it is resolving a promise to retrieve data from the database, I would most likely not wait for it to load and go to one of my other many social media. To keep your user waiting, you would want to create a loading substate by creating a loading.hbs
or application-loading.hbs
inside the templates folder because this will be the default loading template that gets rendered whenever you specify there should be a loading substate. For this whole post, we will refer to this default loading template as application-loading.hbs
.
In order for your loading template to be rendered, any of the beforeModel
, model
, or afterModel
hook needs to return a promise. According to the Ember guide on the loading substates, there is a loading action that you can specify to further customize your loading substate. You can find more information about this in the Ember guide for loading substates.
I am using the promise created here to use as an example for this post. If you add this in any of the hooks mentioned above, you will trigger the loading substate. In the loading template, you can put anything in it to show the user that your website is loading. Conventionally, you would have a spinner to indicate the loading process.
import Ember from 'ember'export default Ember.Route.extend({
// in order to trigger the loading.hbs
// return from store or Ember Data
// or a promise
model() {
// in this case we'll just return a promise for simplicity's sake
let promise = new Promise(function(resolve, reject) {
// on success
Ember.run.later(this, function() {
// resolve anything here
// again, for simplicity's sake
// we will return a string
resolve("test");
}, 3000);});return promise;
}
});// application-loading.hbs
Website is Loading ...
this would be under the index route.
I chose to return the promise in the model hook, but we could have done that in any of the other hooks like beforeModel or afterModel.
Easy right? Ok, well what if you wanted to load the spinner for a nested child routes ? Well that should be easy too and I will show you how to do that.
For nested child routes, we can either use a general template called loading.hbs
within the route folder similar to the one we used for loading the website/application or we can customize it for a specific route.
Let’s create a new route called parentRoute. You can do this by using the CLI tool, which I recommend by typing in the terminal while in your project directory ember g route parentRoute (g is just short for generate).
Going back to folder structure above, we have will need to add a loading.hbs
inside of the parentRoute folder so that any route that is being loaded within parentRoute will render this template instead of the application-loading.hbs
.
To visualize the folder structure, we should have something like this:
// templates folder
templates
|____ application.hbs
|____ application-loading.hbs
|____ parentRoute
|__ parentRoute.hbs
|__ loading.hbs
So far so good? Now we will set a promise to one of the beforeModel, model, or afterModel hook in our parentRoute.hbs
so that it returns a promise and triggers our loading.hbs
.
import Ember from 'ember'export default Ember.Route.extend({beforeModel() {let promise = new Promise(function(resolve, reject) {// on successEmber.run.later(this, function() {
resolve("test");}, 3000);
});return promise;}});// templates/parentRoute/loading.hbsparentRoute is Loading ...
This is in parent route.
After you have done this, you will see that when the application first loads, you will see the application-loading.hbs
render. And then when you navigate to the parentRoute, the loading.hbs
within the parentRoute will render.
To load a specific route loading template, all you have to do is name the template in this way, routeName-loading.hbs
. To show you an example, lets create a route called childRoute within parentRoute. Then, if you wanted to load a different and specific loading template for the childRoute, you would create a loading template called childRoute-loading.hbs
. Then, your folder would look something like this:
// templates folder
templates
|____ application.hbs
|____ application-loading.hbs
|____ parentRoute
|__ parentRoute.hbs
|__ childRoute.hbs
|__ childRoute-loading.hbs
As you can see, Ember has an easy and convenient way for us to create and display loading substates for our users. This also applies for error substates which are very similar in implementation. Gotta keep those users waiting for our beautiful applications!