Global Loader Component using Vue.js and Axios Interceptors

Making of a global loader component that will be invoked from anywhere in the app while making ajax calls using Axios interceptors.

Sandip Shrestha
31 July, 2019 | 4 minutes read
Global Loader Component using Vue.js and Axios Interceptors

We are always optimizing our web pages and applications to open in a zippy. But, sometimes, it is necessary to let the users wait for a brief moment for all the data and pages to load. We will need something to get the users' attention and keep them on the site long enough for all the content to load or to notify them that work is being done in the background. One way to do that is by showing a beautiful, engaging and informative loading animation or loading gif in the website whenever background tasks are running.

Loader Component

Below is the code for a simple Loader component in vue. We have two states isVisible to show/hide the loader and text to define the loading text.

// Loader Component

<template>
    <div v-if="isVisible" class="loader-overlay">
        <div class="loader"></div>
        <span class="text" v-html="text"></span>
    </div>
</template>

<script>
    export default {
        name: "Loader",
        props: {
            isVisible: {type: Boolean, required: true},
            text: {type: String, required: false, default: ""},
        },
}
</script>

<style lang="scss">
    .loader-overlay {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.7);
        z-index: 999;
        cursor: pointer;
        span.text {
            display: inline-block;
            position: relative;
            top: 50%;
            left: 50%;
            transform: translate(-50%,-50%);
            color: #fff;
        }
        .loader {
            animation: loader-animate 1.5s linear infinite;
            clip: rect(0, 80px, 80px, 40px);
            height: 80px;
            width: 80px;
            position: absolute;
            left: calc(50% - 40px);
            top: calc(50% - 40px);
            &:after {
                animation: loader-animate-after 1.5s ease-in-out infinite;
                clip: rect(0, 80px, 80px, 40px);
                content: '';
                border-radius: 50%;
                height: 80px;
                width: 80px;
                position: absolute;
            }
        }
        @keyframes loader-animate {
            0% {
                transform: rotate(0deg)
            }
            100% {
                transform: rotate(220deg)
            }
        }
        @keyframes loader-animate-after {
            0% {
                box-shadow: inset #fff 0 0 0 17px;
                transform: rotate(-140deg);
            }
            50% {
                box-shadow: inset #fff 0 0 0 2px;
            }
            100% {
                box-shadow: inset #fff 0 0 0 17px;
                transform: rotate(140deg);
            }
        }
    }
</style>

Using the loader in our Laravel application

<!DOCTYPE html>
<head>
 <meta charset="utf-8"/>
 <meta content="width=device-width, initial-scale=1" name="viewport"/>
</head>
  
  <body>
    <main id="app">
      @include('flash::message')
      @yield('content')
      {{-- Loader  --}}
      <loader :is-visible="isLoading"></loader>
  
  </main>
 
 </body>
</html>

Call Loader whenever any ajax calls are made

We will need to make the loader work show that it is shown when any ajax calls are made and remove the loader upon getting a response. While using Vue js with Laravel we mostly use axios for ajax calls. Axios has native functionality to intercept request/responses called interceptors.

Axios Interceptors

You can intercept requests or responses to any ajax calls before they are handled by then or catch using interceptors . We will need to remove the request interceptor later on so we will instantiate it and store it as a data axiosInterceptor and just call the response interceptor on mounted() of the main Vue app instance. We are going to define two methods, enableInterceptor to enable the interceptor and `disableInterceptor` to disable the interceptor.

// functions to enable and disable interceptors

// intantiate request interceptor
enableInterceptor() {
  this.axiosInterceptor = window.axios.interceptors.request.use((config) => {/.../});
}
// eject request interceptor
disableInterceptor() {
  window.axios.interceptors.request.eject(this.axiosInterceptor);
}

new Vue({
    el: "#app",
    mounted() {
        this.enableInterceptor()
    },
    data: {
        isLoading: false,
        axiosInterceptor: null,
    },
    methods: {
        enableInterceptor() {
            this.axiosInterceptor = window.axios.interceptors.request.use((config) => {
                this.isLoading = true
                return config
            }, (error) => {
                this.isLoading = false  
                return Promise.reject(error)
            })
            
            window.axios.interceptors.response.use((response) => {
                this.isLoading = false    
                return response
            }, function(error) {
                this.isLoading = false
                return Promise.reject(error)
            })
        },
        
        disableInterceptor() {
            window.axios.interceptors.request.eject(this.axiosInterceptor)
        },     
    },
})

Handling Edge Cases

While showing a loader globally whenever any ajax calls are made is pretty awesome and feels magical, it can have some pitfalls.

For example, I have a slug component that makes an API call to generate a URL on the fly whenever the title changes (i.e on every keypress). However, the loader is shown every time it makes an API call and that is kind of annoying.

To solve this issue we are going to eject the interceptor before making an API call using the disableInterceptor() method described in the above code and re-enable the interceptor after getting a response using the enableInterceptor() method described above.

<template>
    <input type="text" v-model="value">
</template>

<script type="text/ecmascript-6">
    export default {
        name: "slug",
        props: {
            title:{ default:"" }
            apiUrl: { required: true },
        },
        data() {
            return {
                value: "",
                autoGenerate: true,
            }
        },
        watch: {
            title: {
                handler: function(title) {
                  this.generate(title)
                },
            },
        },
        methods: {
            generate(title) {
                const vm = this
                
                this.$root.disableInterceptor()
                
                window.axios.get(vm.apiUrl, {
                    params: {
                        title: title,
                    },
                }).then(function(response) {
                    vm.value = response.data
                    vm.$emit("change", { "slug": response.data })
                    
                    vm.$root.enableInterceptor()
                }).catch(function(error) {
                    console.error(error)
                    
                    vm.$root.enableInterceptor()
                })
            },
        },
    }
</script>
Copyright © 2020, Sandip Shrestha. All rights reserved.