Implementing Instagram's progress-bar with Ember.js

By: Michael Klein | February 28th, 2018

Often when implementing ambitious web applications it's important to let the user know that the app isn't broken when we need to do something async in our application (e.g. fetching data from the api server). The typical way to handle interactions like this is to display a loading-spinner that tells the users that they have to wait a little bit until the data has arrived. In the recent past though some discussion has been popping up around the extensive use of loading spinners on the web[1] and if they actually hurt perceived performance and several applications have decided to take different approaches to show loading progress to their users.

Instagram's web application for example has a very fancy way of telling the user that something async is happening in the background. When switching between different pages of the app the web application will display a nice looking progress-bar on the top of the screen. Here's a small gif illustrating how this actually looks like.

instagram-loading

Super fancy right?! Here's how to rebuild it with Ember.js in your own applications:

The progress-bar-component

The progress-bar component is relatively straight forward to implement. It just displays an element on the page that we position at the top of the page and animate in a certain way (I am using tachyons.css[2] for my css styles):

{{!--  components/ui-progress-bar/template.hbs  --}}

<div class="gradient-animated fixed top-0 left-0 right-0"></div>
// app.scss
.gradient-animated {
 height: 3px;
 background: 
  #27c4f5 
  linear-gradient(
   to right,
   #27c4f5,
   #a307ba,
   #fd8d32,
   #70c050,
   #27c4f5
 );
 background-size: 500%;
 animation: 2s linear infinite barprogress, 0.375s cubic-bezier(0.4, 0.0, 0.2, 1) grow, .3s fadein;
}

@keyframes barprogress {
 0% {
 background-position: 0% 0
 }
 to {
 background-position: 125% 0
 }
}

@keyframes grow {
  from { width: 0%; }
  to { width: 100%; }
}

This looks pretty nice:

progress-bar-component

So now that we got the boring part out of the way now to the fun stuff. To understand the presented solution we need to take a quick detour and look at how the default loading-states in Ember.js actually work.

Loading-States in Ember.js

Ember's router comes with loading states build in. There's a little bit more to it but basically what Ember's router will do when you return a promise from a route's model-hook is to transition into the route's parent-route's designated loading-route and wait for the model-promise to resolve and then transition into the route that we wanted to transition to in the first place. While waiting the designated loading-template is rendered into the parent-route's {{outlet}}. Here's a quick illustration of what happens:

loading-template

This works in certain situations but for what we are trying to achieve this does not work unfortunately.

The obvious first try

To implement a global progress-bar on top of the screen we most likely would try to make use of the default loading-behaviour and do something like this:

{{!--  application.hbs  --}}
{{ui-navigation}}
{{outlet}}
{{!--  loading.hbs  --}}
{{ui-progress-bar}}

If we tried to use this approach and added a loading.hbs-template we would display the loading template in application.hbs's outlet while we are waiting for the profile-route's model-hook to resolve. So far so good but this could actually pose a very small problem because we would need position the progress-bar at a completely different position in the UI via CSS. This might be annoying depending on how your UI is set up but the actual real problem is that we will display only the loading.hbs in the application-template's {{outlet}} and nothing more. This means that we will loose the entire UI of the route that we were on before while waiting.

As mentioned before this might be what you want in certain situations but for displaying a progress-bar at the top of the whole screen this won't do. We can't simply stop showing content to our users while they are waiting and add a loading-bar on top of the screen. Also this approach would get super annoying in the future if we decided to create additional nested routes as we needed to add loading-templates for all nested routes again and again.

Application-route's loading-action to the rescue

So the default loading-template behaviour won't help us. Fortunately there's also another (default) behaviour that we can use for implementing a fancy global progress-bar in our application.

Ember.js-Routes fire a loading-event when they wait for the model-hooks to resolve. This event will bubble up the route-hierarchy until it reaches the application-route of the application where we can handle it via the loading-action. This is the perfect place for us to tell the application-template to display the global progress-bar:

import Route from '@ember/routing/route';
import { getOwner } from '@ember/application';
import { set } from '@ember/object';

export default Route.extend({
  // ...
  actions: {
    loading() {
      this._showGlobalLoadingIndicator(...arguments);
    }
  },
  
  _showGlobalLoadingIndicator(transition) {
    let applicationController = getOwner(this).lookup('controller:application');

    if (!applicationController) {
      return;
    }

    set(applicationController, 'isLoading', true);

    transition.promise.finally(() => {
      set(applicationController, 'isLoading', false);
    });
    
    return true;
  }
});

And then in application.hbs:

{{!--  application.hbs  --}}
{{if isLoading}}
  {{ui-progress-bar}}
{{/if}}
{{ui-navigation}}
{{outlet}}

This gives us exactly the behaviour that we want. The gif shows that the existing ui still stays in place while we wait for data to get back to the user and after the model hooks resolves we transition over to the new route.

ember-instagram-loading

The nice thing about this approach is that this won't break the default behaviour for subroutes. So if certain subroutes need to handle loading-behaviour the usual way this still works but for those that don't implement a custom loading-ui we will show this global loading behaviour.

Overall I think this is a very clean solution to showing a global progress-bar in Ember.js-applications and a very fancy UI to indicate a loading state to your users.

Thanks for reading and as always just get in touch if you have questions. I'm available for consulting work and would be happy to help you and your company with your Ember.js-projects. You can have a look at the services that I provide here.


  1. See this blog post about loading states for example ↩︎

  2. Tachyons.io ↩︎

Effective-Ember
© Effective Ember. Ember.js Consulting & Training by Michael Klein. All rights reserved. Impressum
This website uses cookies.