Press "Enter" to skip to content

VueJS – Create and use loader spinner

VueJS loader

Loaders or spinners are so popular that it is quite obvious when do we use it – typically – when some content is loading, by using ajax request or something is processing in the background. We can use loeaders in VueJS in very nice and clean manner by using VueJS functionality – like components, lifecycle hooks and props. With that we can build reusable loader-spinner component which will work in the same manner always when we need to do e.g. async request.

Just for your information – I started in my example VueJS project directly from Vue CLI.

In this article we will create a Loader-spinner component with demo app like in frame below.

Click “Refresh data” button to see loader again.

1. Loader-spinner component

First thing here is to create a new component for a spinner. This will be very small component. As we are using Vue CLI, then of course, we use single component files with .vue extension, so there is HTML (template), JavaScript and CSS code.

<template>
  <div v-if="active" class="loader-wrapper">
    <div class="loader">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
    <p>{{ text }}</p>
  </div>
</template>

<script>
export default {
  name: 'Loader',
  props: {
    active: Boolean,
    text: String
  }
}
</script>

<style scoped>
  p {
    font-size: 0.8em;
    font-weight: 300;
    margin-top: 5px;
    letter-spacing: 1px;
    color: rgb(82, 82, 82);
  }

  .loader-wrapper {
    text-align: center;
  }

  .loader {
    display: inline-block;
    position: relative;
    width: 80px;
    height: 80px;
  }
  .loader div {
    box-sizing: border-box;
    display: block;
    position: absolute;
    width: 64px;
    height: 64px;
    margin: 8px;
    border: 8px solid #6916a0;
    border-radius: 50%;
    animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
    border-color: #6916a0 transparent transparent transparent;
  }
  .loader div:nth-child(1) {
    animation-delay: -0.45s;
  }
  .loader div:nth-child(2) {
    animation-delay: -0.3s;
  }
  .loader div:nth-child(3) {
    animation-delay: -0.15s;
  }
  @keyframes loader {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
</style>

Let’s check code above step by step:

  1. The most important thing here is the v-if="active" statement. The active variable is a props received from parent (hosting) component, which tells loader to show or hide.
    The second prop used here is message – this text also has v-if statement, so it is visible only in message is provided by the parent component.
  2. Component’s JavaScript code is very simple – it contains only name of component and props definition.
  3. We use here loader as CSS animation, so the CSS code is more advanced, but more or less – this is a normal CSS animation, you can find it a lot on the internet.

And loader component code in parent component is as following:

<loader :active="loaderActive" message="Please wait 5 seconds" />

Where loaderActive variable is passed as :active prop – so change true/false of loaderActive from parent component, manages visibility of loader.

2. How to use loader-spinner component

To show you how to use loader component I have created very basic Vue CLI app. Long story short:

This app simulate ajax request to get employees list from the server. When application is waiting for the server response – the loader-spinner is displayed. When employees list is downloaded – the table is visible. There is also a button to refresh employees list download simulation.

What are the most important things in using loader in optimized and clean way? Let’s go step by step by key features of vue-loader-spinner demo app:

  1. Move whole loader functionality into mixin – mixin something like a part of component shared between many components. Read more here in docs.
    export default {
      data: () => {
        return {
          loaderActive: false,
        }
      },
      methods: {
        showLoader () {
          this.loaderActive = true;
        },
        hideLoader () {
          this.loaderActive = false;
        },
      }
    }
  2. In main component of demo app App.vue we have basic functionality:
    – loader triggered by loaderActive variable (from mixin)
    – container with refresh data button and the table with employees list. It’s visibility is turned when employees data is inside employees array.
  3. In JavaScript code we import first all needed external files and declare component with our mixin import, methods definitions and mounted VueJS lifecycle hook. What is important now and what is the right approach:
    – if the data (employees table) must load just after page is opened, then request must start just when component is built, so mounted hook is the best choice. There we run this.loadData(); method.
  4. We can have also some kind of button which will trigger ajax call on user click (not when page is opened). In our example we have Refresh data button.
  5. Data request loadData() method must have always – on the beginning – activation of loader – this.showLoader() method from mixin, and after data download success or reject – hiding of loader – this.hideLoader() method from mixin.

Related App.vue code:

<template>
  <div id="app">
    <h1>VueJS loader-spinner</h1>
    <loader :active="loaderActive" message="Please wait 5 seconds" />

    <div v-show="employees.length">
      <button @click="refreshData">Refresh data</button>

      <table>
        <thead>
          <tr>
            <th>index</th>
            <th>age</th>
            <th>eyeColor</th>
            <th>name</th>
            <th>gender</th>
            <th>company</th>
            <th>email</th>
            <th>phone</th>
            <th>salary</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="row in employees" :key="row.index">
            <td>{{row.index}}</td>
            <td>{{row.age}}</td>
            <td>{{row.eyeColor}}</td>
            <td>{{row.name}}</td>
            <td>{{row.gender}}</td>
            <td>{{row.company}}</td>
            <td>{{row.email}}</td>
            <td>{{row.phone}}</td>
            <td><input type="number" v-model="row.salary"></td>
          </tr>
        </tbody>
      </table>
    </div>

  </div>
</template>

<script>
import Loader from './components/Loader.vue'
import { employeesJson } from './mock-data/employees';
import loaderMixin from './mixins/loader';

export default {
  name: 'App',
  components: {
    Loader
  },
  mixins: [loaderMixin],
  data: () => {
    return {
      employees: []
    }
  },
  methods: {
    loadData () {
      this.showLoader();
      setTimeout(() => {
        this.employees = JSON.parse(employeesJson);
        this.hideLoader();
      }, 5000);
    },
    refreshData () {
      this.employees = [];
      this.loadData();
    }
  },
  mounted () {
    this.loadData();
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

table {
  width: 100%;
  max-width: 1200px;
  margin: 20px auto;
}

button {
    padding: 10px;
    background: #07ab85;
    color: #fff;
    font-weight: bold;
    border: none;
    text-transform: uppercase;
    border-radius: 5px;
}

</style>

You can check and download whole code of Loader-spinner component with the demo app in our GITLAB HERE.