A Template That Renders While `model()` Awaits
Loading Substates
A loading.hbs template renders during slow transitions — global or per-route — so users see a spinner or skeleton instead of a stale page.
What you'll learn
- Add a top-level app/templates/loading.hbs
- Add per-route loading templates
- Combine loading state with skeleton UIs
When a route’s model() returns a Promise, the user is waiting. Ember auto-renders a loading substate for you if one exists — convention beats wiring.
Global Loading
{{! app/templates/loading.hbs }}
<div class="app-loader">
<Spinner />
<p>Loading…</p>
</div> This renders during any transition where the parent’s model is still pending.
Per-Route Loading
For the posts route, create app/templates/posts/loading.hbs:
{{! app/templates/posts/loading.hbs }}
<div class="posts-skeleton">
{{#each (array 1 2 3 4 5) as |n|}}
<div class="skeleton-row" />
{{/each}}
</div> Per-route templates win over the global one when both exist.
Loading Route Hook
You can also implement loading as a route action:
// app/routes/posts.js
import Route from '@ember/routing/route';
import { action } from '@ember/object';
export default class PostsRoute extends Route {
@action
loading(transition) {
// Returning true (or letting it bubble) renders the loading substate
// Returning false short-circuits, hiding the substate
return true;
}
} Return false if you want a “stay on the previous page until loaded” experience.
When Substates Trigger
Ember only renders a loading substate if model(), beforeModel(), or afterModel() returns a Promise that is still pending. Synchronous returns skip it entirely.