Making of a global loader component that will be invoked from anywhere in the app while making ajax calls using 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.
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>
<!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>
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.
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)
},
},
})
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>