I have a simple problem.
I'm using Vue3 with vite, typescript and router, and I can't figure out, how to trigger a loading screen whilst the user clicks on router paths.
I successfully understood and implemented that within the router/index.ts
, I can call a router.beforeEach
, in which I can toggle a variable. Called it isLoading
, and put a timeout
delay before another route is rendered, it looks as follows:
var isLoading = false
router.beforeEach((to, from, next) =>{
console.log("beforeach");
isLoading = true
console.log(isLoading)
setTimeout(() => {
next();
}, 2000);
});
router.afterEach((to, from) => {
console.log("aftereach");
var isLoading = false
console.log(isLoading)
});
In my App.vue, I have a Preloader
component imported from my views
folder, which contains a simple div with some styling, and I want this component to get a class on beforeEach
, and that class to be gone on afterEach
.
But I have no idea, how to acquire the state of isLoading
from my router/index.ts
, or, if there is another way purely within App.vue that could trigger a class to my PreLoader component.
Thanks for your help!
Tried to use beforeEach and afterEach with a delay within router/index.ts, but don't know how to export the state of isLoading to App.vue.
CodePudding user response:
The following two approaches can help you-
1. Using event bus-
- Emit an event on the router's before and after hook and pass a status true/false.
router.beforeEach((to, from, next) =>{
eventbus.emit('loader-status', true)
});
router.afterEach((to, from) => {
eventbus.emit('loader-status', true)
});
- Listen to this event in your loader component and show/hide the loader as per the status passed by the event.
eventbus.on('loader-status', status => { this.showLoader = status })
Here you can read- How to use event buses in vue3.
2. Using Vuex-
- Create a variable named
showLoader
in the Vuex state. - Update that variable on routers before and after the hook.
router.beforeEach((to, from, next) =>{
// call a mutation to update the showLoader to true
});
router.afterEach((to, from) => {
// call a mutation to update the showLoader to false
});
- Use this global variable's value in the Loader component to show/hide the loader.
<Loader v-if="store.state.showLoader"></Loader>
Here you can read- How to set up Vuex in vue3.
NOTE-
I only provided the logical code, syntax would be different when you use any of the above approaches.
CodePudding user response:
so thanks to Neha Soni's answer, I found out about
Pinia
and what I had in mind, looks like the following:
I installed pinia, created a store
folder under src
, and created there an isloading.ts
file, which looks like this:
import { defineStore } from "pinia";
import { reactive, ref } from "vue";
export const useLoaderState = defineStore("isLoading", () => {
const state = ref(false)
const changeStateTrue = () => {
state.value = true
}
const changeStateFalse = () => {
state.value = false
}
return { state, changeStateTrue, changeStateFalse }
})
In router/index.ts
, I have the following logic that awaits for a bit before actually rendering the clicked route, and saves to the global variable isLoading as state a boolean:
router.beforeEach((to, from, next) => {
const isLoading = useLoaderState();
const { changeStateTrue } = isLoading;
console.log("beforeach");
changeStateTrue();
console.log(isLoading.state);
setTimeout(() => {
next();
}, 500);
});
router.afterEach((to, from) => {
const isLoading = useLoaderState();
const { changeStateFalse } = isLoading;
console.log("aftereach");
changeStateFalse();
console.log(isLoading.state);
});
Finally, in my App.vue
I have the following for my PreLoader, which is just for a loading screen:
<script setup lang="ts">
import { useLoaderState } from "./store/isloading";
const isLoading = useLoaderState();
</script>
<template>
<PreLoader v-if="isLoading.state" : />
</template>
I wanted even my loader to change according to the already applied dark/light mode, so thats what the :
is there for, what is important is the v-if="isLoading.state"
, which first gets into this component with calling the Pinia way the isLoading
global variable with
- importing the defined store with
import { useLoaderState } from "./store/isloading";
- creating a constant in this current
App.vue
with theconst isLoading = useLoaderState();
From there, the constant's values and states (including the boolean which I wanted) lives reactively in this current component (in this case, the App.vue
), and the v-if
will get updated therefore dynamically.