Add Loading Indicators to Your Vue.js Application

Publikováno: 16.4.2018

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, the...

Celý článek

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 users anxiety and causes them to think that the site has broken, their action failed and so many negative thoughts. Users end up leaving websites (we don't want that). So 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 essentially what we want:

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

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 and dirty 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:

Totally legit

Async Vue components

Vue's first class support for asynchronous components is nothing short of amazing. If you don't know what async components are? They basically let you fetch components from the server only when you need them.

So instead of serving end users components they might never use — only give them what they need. You can read more about them in the official document.

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 I used above, you need to use Vue router lazy loading. It's async components on steroids.

Using Vuex plugins

What this means is that you could create a custom Vuex plugin that auto triggers a loader. While this sounds like a feasible solution, I think it is hard to maintain and control the loading state and don't recommend it.

Honestly, I don't really know a way to make this work well out the box, it requires a lot of finagling to work.

Conclusion

There are probably a few ways to define loading indicators in Vue apps that I didn't cover. There are also Vue dedicated plugins for doing this, but, I felt it made more sense to get a feeling for how it works.

Let me know in the comments if you think I missed a crucial method and I'll update the article.

Have a great day.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace