Loading indicator with every async request — Vue edition

Setup a loader store module

export const loader = {
namespaced: true,
state: {
loading: false
},
actions: {
show({ commit }) {
commit("show");
},
hide({ commit }) {
commit("hide");
}
},
mutations: {
show(state) {
state.loading = true;
},
hide(state) {
state.loading = false;
}
}
};
import Vue from "vue";
import Vuex from "vuex";
import { loader } from './modules/loader';Vue.use(Vuex);export const store = new Vuex.Store({
state: {
},
modules: {
loader
}
});

Create a loader component

<template>
<div
class="loader"
:class="{ 'loader--visible': visible }"
>
Loading
</div>
</template>
<script>
export default {
name: "loader",
props: ["visible"]
};
</script>
<style lang="scss">
.loader {
background: rgba(255, 255, 255, 0.8);
transition: 0.15s ease-in opacity;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
opacity: 0;
&--visible {
opacity: 1;
pointer-events: all;
}
}
</style>

Custom HTTP client and request/response interceptors

import axios from "axios";export default axios.create();
import httpClient from "./httpClient";
const actionScope = `loader`;
export function setupInterceptors({ dispatch }) {
let requestsPending = 0;
const req = {
pending: () => {
requestsPending++;
dispatch(`${actionScope}/show`);
},
done: () => {
requestsPending--;
if (requestsPending <= 0) {
dispatch(`${actionScope}/hide`);
}
}
};
httpClient.interceptors.request.use(
config => {
req.pending();
return config;
},
error => {
requestsPending--;
req.done();return Promise.reject(error);
}
);
httpClient.interceptors.response.use(
({ data }) => {
req.done();
return Promise.resolve(data);
},
error => {
req.done();
return Promise.reject(error);
}
);
}

Putting everything together

import Vue from 'vue'
import App from './App.vue'
import { store } from './store';
import { setupInterceptors } from './utils/httpInterceptors';
Vue.config.productionTip = falsenew Vue({
render: h => h(App),
created() {
setupInterceptors(store);
},
store
}).$mount('#app')
<template>
<div id="app">
<Loader :visible="loading"/>
</div>
</template>
<script>
import { mapState } from "vuex";
import Loader from "@/components/loader";
import httpClient from '@/utils/httpClient';
export default {
name: "app",
components: {
Loader
},
computed: {
...mapState("loader", ["loading"])
}
};
</script>
<style>
</style>

Using HTTP client

import httpClient from '@/utils/httpClient';httpClient.get('yourApiUrl');

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store