Tutorial

How To Add Loading Indicators to a Vue.js Application

DevelopmentJavaScriptVue.js

Introduction

Loading indicators improve UX (user experience) in any application—web or mobile. These animations provide feedback to the user that an action is being carried out 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 introduce a few ways you can add loaders to your Vue.js applications.

Prerequisites

To complete this tutorial, you will need:

This tutorial was verified with Node v14.2.0, npm v6.14.5, vue v2.6.11, and vue-router v3.1.6.

Step 1 — Setting up the Project

Let’s create a new Vue project. This tutorial will use Vue CLI (command line interface) to scaffold your application:

  • npx @vue/cli@4.5.4 create --inlinePreset='{ "useConfigFiles": false, "plugins": { "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-eslint": { "config": "base", "lintOn": ["save"] } }, "vuex": true, "router": true, "routerHistoryMode": true }' vue-loading-indicators

This long command is a set of presets based on defaults established by @vue/cli/packages/@vue/cli/lib/options.js. When reformatted for readability, it looks look like this:

{
  "useConfigFiles": false,
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "config": "base",
      "lintOn": ["save"]
    }
  },
  "vuex": true,
  "router": true,
  "routerHistoryMode": true
}

These presets add vue-router as a plugin (cli-plugin-router), add vuex as a plugin (cli-plugin-vuex), enable history mode, add Babel, and add ESLint.

For the needs of this tutorial, you will not require TypesScript, Progressive Web App (PWA) support, Vuex, CSS pre-processors, unit testing, or end-to-end (E2E) testing.

Next, change into the new project directory:

  • cd vue-loading-indicators

You can view your application in your web browser by running the following command:

  • npm run serve

When you visit localhost:8080 in a web browser, you should see a "Welcome to Your Vue.js App" message. There will also be links to a Home page and About page since you included Vue Router. Your goal will be to add a loading indicator when naviagting between these two pages.

If you want to learn more about Vue CLI, you can refer to the Vue CLI documentation.

Now, you are ready to start building.

Step 2 — Using nprogress

You can use any loading indicator of your choice. For this tutorial, you will install nprogress to use as your loading indicator.

You won’t be using npm or yarn for this. You will reference it from a CDN (content delivery network).

In the created Vue CLI project, navigate to public/index.html file:

  • nano public/index.html

And add the snippet below before the closing </head> tag:

public/index.html
<head>
  <!-- ... -->
  <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>
</head>

nprogress exposes a few API methods, but for this article, you will require two—start and done. These methods start and stop the progress bar.

nprogress can also be configured for how to progress the loader. Although this can be manually customized, this article will use the default behavior.

Step 3 — Using the Router

When you use the router to add a progress bar to your website, the typical functionality you expect would be:

“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.”

Animated gif depicting a loading progress bar across the top of the screen.

Vue Router comes with hooks you can hook into that lets you accomplish this functionality.

Open the src/router/index.js file:

  • nano src/router/index.js

And add the code below before the export:

src/router/index.js
// ...

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 you hook to the beforeResolve route, you 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 to stop the progress bar, it shouldn’t care if the page request succeeds.

At this point, you can run your application:

  • npm run serve

When you visit localhost:8080 in your web browser, you can navigate between the Home page and the About page. As you do so, you will see the loading indicator that you added to your application.

The next step will further explore loading indicators.

Step 4 — Exploring Advanced Usage

In this section, you will be introduced to other use cases for loading indicators in your application.

These will be presented with examples and you will not be directly implementing them in your tutorial unless you feel like exploring on your own.

Using a HTTP library

Another part of the application for which you may like to add progress bars is when your 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, you can also hook into your library of choice.

For this tutorial, you will be using axios. This library uses the term interceptors.

Install axios:

  • npm install axios@0.20.0

Then create an HTTP.js file:

  • nano HTTP.js

Then, you can configure axios to work like this:

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

This code allows you to handle your connections and get a progress bar every time a request is made.

Using Loaders within Components

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

Let’s consider 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>

The example component would resemble something like this:

Note: Some of the finer details of SVG viewBox, path, and style are not defined in this example.

src/components/DownloadButton.vue
<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/>
    </span>
  </button>
</template>

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

<style>
  /* ... */
</style>

Here is a demonstration of what this code would produce:

Animated gif of user interacting with a download button and a loading circle appearing.

Using Higher-Order Components for Reusable Loaders

You can create loaders as wrappers (HOCs) for our components that you can then modify their 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.

Here is an example:

Note: Some of the finer details of the contents of Stats are not defined in this 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 is a demonstration of what this code would produce:

Box with an update button, the user clicks update, and the box grayed out to display a loading message

Using Asynchronous Vue Components

Vue’s asynchronous components allows you to fetch components from the server only when you need them. Instead of serving end users components they might never use, end users are served only what they need.

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

Here is an example:

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, you explored some of the ways to add a loading indicator to your Vue.js app. Loading indicators are a useful tool to provide feedback to users.

If you’d like to learn more about Vue.js, check out our Vue.js topic page for exercises and programming projects.

Creative Commons License