My component is like this :
...
You simply need to attach it to a flag and use v-if
, if you're using vue resource, you can set the loading
flag to true in the before
callback and set it back to false
after you receive the response:
Vue Instance
methods: {
loadData() {
this.$http.get('/search', {
before: () => {
this.loading = true;
}
}).then(response => {
// Deal with response
}).then(() => {
//set loading flag to false
this.loading = false;
})
}
},
data: {
loading: false
}
HTML
<div id="app">
<button @click="loadData">
Get Data
</button>
<!-- Only show if loading is true -->
<div v-if="loading" v-cloak>
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
<span>Loading...</span>
</div>
</div>
Here's the JSFiddle: https://jsfiddle.net/hyeycoan/
You could add a loading div on your template, and toggle it's display based on loading
flag. Something like this
new Vue({
el: '#app',
data: {
show: true,
isLoading: false,
},
methods:{
loadData: function(){
this.isLoading = true;
setTimeout(function(){
this.isLoading = false;
}.bind(this),1000);
}
}
})
.loading{
display: none;
position: absolute;
width: 100%;
height: 100%;
top: 0;
background: rgba(128, 128, 128, 0.5);
}
.loading.show{
display: initial;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<div class="loading" v-bind:class="{ show: isLoading }">
<span>Loading</span>
</div>
<button @click="loadData">Load</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
</html>
You could make the loading indicator pretty with css, or you can use already available one, for example you could use http://tobiasahlin.com/spinkit/, http://loading.io/
For usability's sake, I'll suggest using a loader which has its own vuex state.
First define where you would need this particular loader:
If your loader is not tightly coupled to any component like in case 1. Then it would make more sens to keep your loader in your main vue file (if you are using vue-cli then App.vue)
Something like so:
<template>
<div id="app">
<loader></loader>
<router-view></router-view>
</div>
</template>
<script>
import Loader from './components/shared/loader/Loader'
export default {
name: 'app',
components: {
Loader
}
}
</script>
With this, you don't have to add loader.vue in every other component file. But first, I'll show you the loader component and store I am using.
<template>
<div class='loader-container' :class='{"show": show, "hidden": !show}'>
<div class="curved-div">
<div class="colour-magic">
<i class='fa fa-circle-o-notch rotate'></i>
</div>
<div class="loading">
{{ loading }}
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import * as NameSpace from '../../../store/NameSpace'
export default {
data () {
return {
loading: 'Loading...'
}
},
computed: {
...mapGetters({
show: NameSpace.GET_LOADER_STATE
})
}
}
</script>
<style scoped>
.loader-container {
position: fixed;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
}
.curved-div {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%);
border-radius: .3rem;
width: 20rem;
padding:1rem;
background: white;
box-shadow: 0 0 .1rem #fefefe;
}
.curved-div > * {
display: inline-block;
}
.rotate {
border-radius: 50%;
padding: .5rem;
animation-name: rotate;
animation-duration: .7s;
animation-iteration-count: infinite;
animation-delay: 0s;
}
.loading {
text-align: center;
width: 12rem;
font-size: 1.8rem;
}
.show {
visibility: visible;
opacity: 1;
z-index: 1;
transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}
.hidden {
opacity: 0;
visibility: hidden;
z-index: 0;
transition: opacity 0.5s ease-out, visibility 0.5s ease-out, z-index 0.5s ease-out;
}
@keyframes rotate {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
.colour-magic {
animation-name: colorMagic;
animation-duration: 20s;
animation-iteration-count: infinite;
animation-delay: 0s;
}
@keyframes colorMagic {
0% { color: rgb(179,10,10); }
10% { color: rgb(227,132,22); }
20% { color: rgb(164,153,7); }
30% { color: rgb(26,171,19); }
40% { color: rgb(19,144,177); }
50% { color: rgb(14,16,221); }
60% { color: rgb(27,9,98); }
70% { color: rgb(58,11,111); }
80% { color: rgb(126,14,129); }
90% { color: rgb(208,19,121); }
100% { color: rgb(198,18,18); }
}
</style>
Please note that I am using font-awesome for the loader.
and here is the store:
import * as NameSpace from '../NameSpace'
// you can also use the namespace: true in your store and eliminate the need of NameSpace.js
const state = {
[NameSpace.LOADER_STATE]: false
}
const getters = {
[NameSpace.GET_LOADER_STATE]: state => {
return state[NameSpace.LOADER_STATE]
}
}
const mutations = {
[NameSpace.MUTATE_LOADER_STATE]: (state, payload) => {
state[NameSpace.LOADER_STATE] = payload
}
}
const actions = {
[NameSpace.LOADER_SHOW_ACTION]: ({ commit }, payload) => {
commit(NameSpace.MUTATE_LOADER_STATE, payload)
}
}
export default {
state,
getters,
mutations,
actions
}
A usage example:
// This is not a .vue file it is a .js file, therefore a different way of using the store.
import Vue from 'vue'
import * as NameSpace from 'src/store/NameSpace'
import loaderState from 'src/store/modules/loader'
/**
* Pass the mutation function to reduce the text length
* This function can now be used in the api calls to start/stop the loader
* as the api starts and finishes.
*/
let loaderSwitch = loaderState.mutations[NameSpace.MUTATE_LOADER_STATE].bind(null, loaderState.state)
login (username, password) {
loaderSwitch(true)
return new Promise((resolve, reject) => {
SomeEndpoint.logIn(username, password, {
success (user) {
loaderSwitch(false)
resolve(user.attributes)
},
error (user, error) {
loaderSwitch(false)
reject(errorHelper(error.code))
}
})
})
Now, irrespective of the component where login is used, the loader component need not be kept there.