Tutorial

How To Add Loading Indicators to Your Vue.js Application

DevelopmentJavaScriptVue.js

While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

Loading indicators improve UX (user experience) in any application web or mobile. It tells the user that an action is being carried and a result will return shortly.

In web applications, there are two major events that need loaders:

  • Network requests like navigating to a different page or an AJAX request.
  • When a heavy computation is running.

This article will show a few ways we can add loaders to our Vue.js applications.

Types of loading indicators

The type of loading indicator you use on your website has either a positive or negative effect on the user. Primarily, there are two types of indicators we can use on our websites.

The first is a counter. It can be in the form of a progress bar or a counter that counts from zero to hundred. This is the most effective type of loading indicator as it gives the user a rough estimate of when the action they performed will complete.

The second is a loop. This type of loader is some sort of running animation. The most common example we see on the web is a spinner.

While the counter loading indicator is most suitable for long running tasks, the loop indicator is perfect for short running tasks.

If you use a looping indicator for a long-running task, it builds the user’s anxiety and causes them to think that the site iss broken, their action failed, and leave with negative thoughts. Therefore for long running tasks, avoid looping indicators.

Either type of loading indicator is fine for short tasks even though looping indicators much better.

Getting started

Let’s create a new project. I’ll be using Vue CLI to scaffold our application. If you don’t know what Vue CLI is, you can watch this.

  • vue create indicator

The CLI will ask us what technologies we want to use in our application, for this demo, I chose Router and Vuex. Next, hit enter and wait while your application installs its dependencies.

Vue CLI create application prompt

We’ll also install nprogress to use as our loading indicator. You can use any loading indicator of your choice.

nprogress is a progress bar that attaches itself to the top of the page

We won’t be using npm or yarn for this, let’s pull it straight from the CDN.

In the created Vue CLI project, navigate to public/index.html file and add the snippet below before the closing head tag.

<link href="https://unpkg.com/nprogress@0.2.0/nprogress.css" rel="stylesheet" />
<script src="https://unpkg.com/nprogress@0.2.0/nprogress.js"></script>

To use nprogress

nprogress exposes a few API methods, but for this article we are only interested in the start and done methods. These methods start and stop the progress bar.

nprogress will also figure how to progress the loader, although this can be manually decided, we’ll stick with the default behaviour for demonstration.

Using the Router

When we use the router to add a progress bar to our website, the functionality we often want is: When a user navigates to a new page, the loader starts ticking at the top of the page showing the user the download progress of the next page.

This is what we want:

Gif with loading progress bar

The good thing is Vue router comes with hooks we can hook into that lets us do this.

Open the src/router.js file and replace the default export with the code below.

const router = new Router({
  routes: [
      { path: '/', name: 'home', component: Home },
      { path: '/about', name: 'about', component: About }
  ]
})

router.beforeResolve((to, from, next) => {
  // If this isn't an initial page load.
  if (to.name) {
      // Start the route progress bar.
      NProgress.start()
  }
  next()
})

router.afterEach((to, from) => {
  // Complete the animation of the route progress bar.
  NProgress.done()
})

export default router

When we hook to the beforeResolve route, we are telling the router to start nprogress once a page request happens. The afterEach hook tells the router that after a link has completely evaluated stop the progress bar, it shouldn’t care if the page request succeeds.

Using your HTTP library

Another part of the application for which we like to add progress bars is when our user makes an AJAX request.

Most HTTP libraries today have a sort of middleware or interceptor that fires before a request or response happens. Because of this, we can also hook into our library of choice.

I prefer using axios. They call it interceptors. To install:

  • # for yarn
  • yarn add axios
  • # for npm
  • npm install --save axios

Then we can configure axios to work like this.

// in HTTP.js
import axios from 'axios'

// create a new axios instance
const instance = axios.create({
  baseURL: '/api'
})

// before a request is made start the nprogress
instance.interceptors.request.use(config => {
  NProgress.start()
  return config
})

// before a response is returned stop nprogress
instance.interceptors.response.use(response => {
  NProgress.done()
  return response
})

export default instance

Then you can import and use the file above to handle your connections and get a progress bar every time a request is made.

Loaders within components

There are times when we are not making a page request or an AJAX request. It may just be a browser dependent action that takes time.

Let’s create a custom DownloadButton component that can change its state to a loading state due to some external action.

The component will take only one prop loading.

<template>
  <DownloadButton :loading="loading">Download</DownloadButton>
</template>

Defining the component might look like this.

<template>
  <button class="Button" :disabled="loading">
      <svg v-if="loading" class="Button__Icon Button__Spinner" viewBox="...">
          <path d="..."/>
      </svg>
      <svg v-else class="Button__Icon" viewBox="0 0 20 20">
          <path d="..."/>
      </svg>
      <span v-if="!loading" class="Button__Content">
          <slot></slot>
      </span>
  </button>
</template>

<script>
export default {
  props: {
      loading: { type: Boolean }
  }
}
</script>

<style>
/* styles go here... duh! ;) */
</style>

Here’s a quick example.

Gif of user clicking on download and a loading circle appearing

HOCs for Reusable Loaders

We can create loaders as wrappers (HOCs) for our components that we can then modify its state via props.

These type of loaders are good for components that don’t affect the global state of your application, but you still want the user to feel connected to the action in place.

A quick example:

// This loader will add an overlay with the text of 'Loading...'
const Loader = {
  template: `
      <div class="{'is-loading': loading}">
          <slot/>
      </div>
  `,
  props: ['loading']
}

const Stats = {
  template: `
      <Loader :loading="updating">
      ...
      </Loader>
  `,
  props: ['updating']
}

const app = new Vue({
  template: `
  <div class="widget">
      <Stats :updating="fetchingStats"/>
      <Button @click="fetchingStats = true">
          Latest stats
      </Button>
  </div>
  `,
})

Here’s an example of what it might look like:

box with an update button, the user clicks update and the box greys to "loading"

Async Vue components

Vue’s asynchronous components let you fetch components from the server only when you need them. Instead of serving end users components they might never use — they’re only given them what they need. You can read more about them in the official documentation.

Async components also come with native support for loading and error states, so no need for any special configuration here.

const AsyncComponent = () => ({
  component: import('./MyComponent.vue'),
  // show this component during load
  loading: LoadingComponent,
  // show this component if it fails to load
  error: ErrorComponent,
  // delay before showing the loading component
  delay: 200,
  // error if the component failed to loadin is allotted time in milliseconds default in Infinity
  timeout: 3000
})

To use async components with the method here, you need to use Vue router lazy loading.

Conclusion

In this article we explored some of the ways to add a loading indicator to your Vue.js app.

Creative Commons License