How to use createAsyncComponent in VueRouter#4.0.0 (Vue3)? - vue.js

When I fill the component field in createRouter() like this:
{
path: '/article/post',
name: 'article-post',
component: defineAsyncComponent({
loader: () => import('#/views/article-post/index.vue'),
loadingComponent: () => import('#/views/article-post/skeleton.vue'),
}),
},
Seems like it's not working.
What I want it to show a loading page when the actual page is loading.
How do I do that?

The loadingComponent value must be a component definition, and cannot itself be an async component:
import { createRouter } from 'vue-router'
import { defineAsyncComponent } from 'vue'
import Skeleton from '#/views/article-post/skeleton.vue'
export default createRouter({
routes: [
{
path: '/article/post',
name: 'article-post',
component: defineAsyncComponent({
loader: () => import('#/views/article-post/index.vue'),
//loadingComponent: () => import('#/views/article-post/skeleton.vue'), ❌ cannot be dynamic import
loadingComponent: Skeleton ✅
}),
},
]
})
Also note that the loading component (Skeleton) is only shown once, i.e., if the component definition is not already in cache. This Codesandbox adds an artifical delay in loader() to demonstrate.

You can load components asynchronously like this:
{
path: '/article/post',
name: 'article-post',
component: () => ({
component: import('#/views/article-post/index.vue')
loading: import('#/views/article-post/skeleton.vue')
})
},

Note
Do not use Async components for routes. Async components can still be used inside route components but route component themselves are just dynamic imports.

Related

Vue2 - change the route based on store value

I am trying to change the route of my components based on a value saved within the vuex store. There are four different template files, saved within different directories (template1, template2 etc). I am pulling the template id from the details store and depending on this value would like to render the component from the correct directory.
For example, if the template value in the store is 2 then load all the template2/profile component, if it is three load the template3/profile component.
import VueRouter from 'vue-router'
import store from '../store';
Vue.use(VueRouter)
const setComponent = componentName => {
return () => import(`../template${store.getters['detailsStore/getTemplate']}/views/${componentName}`)
}
const routes = [
{
path: '/',
redirect: '/details'
},
{
path: '/profile',
name: 'Profile',
//component: () => import('../template2/views/Profile') // - this is how i was importing the component but couldn't access vuex store
component: () => setComponent('Profile'),
},
{
path: '/details',
name: 'Details',
component: () => setComponent('Details'),
}
];
const router = new VueRouter({
mode: 'history',
routes
})
export default router
I thought creating the setComponent function would help,. but i just get a blank page. Any help would be much appreciated
Vue2:
Your component is not done loading. This will do it:
const asyncComp = async (componentName) => {
const component = await import(`../template${store.getters['detailsStore/getTemplate']}/views/${componentName}`)
return component;
};
{
path: '/profile',
name: 'Profile',
component: () => asyncComp('Profile'),
}
Vue 3 solution:
Here you will have defineAsyncComponent() available
https://vuejs.org/api/general.html#defineasynccomponent
const asyncComp = (compName) => defineAsyncComponent(() => import(`../template${store.getters['detailsStore/getTemplate']}/views/${compName}`);

Process 404 page when there is no parameter in Vue

Dynamic routing is in use.
If there is no device data in vuex, I want to go to 404 page.
How should I implement it?
router/index.js
const routes = [
{
path: '/',
name: 'Main',
component: Main
},
{
path: '/:device',
name: 'Detail',
component: Detail,
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
},
]
When the device-detail page is implemented as follows, it does not move to the 404 page.
const deviceName = route.params.device
const storedDeviceList = computed(() => store.state.stationName)
if (!storedDeviceList.value.includes(deviceName)) {
router.push({
name: 'NotFound'
})
}
I think the first problem is, that you declare router two times in your project, according to your github repo. You declared your routes in your router/index.js and imported it into your main.js. So importing it again in About.vue from vue-router instead of router.js causes, that this instance has no routes. The second problem is the same with your store, as you import store/index.js to your main.js but import a new instance from vuex to your About.vue.
If you would use the composition API, you could call the already in main.js imported modules with this, like:
this.$router.push({
name: 'NotFound'
})
You also would get your states from your store like this:
this.$store.state.stationName
So, in composition API, use something like this in your About.vue:
<script>
export default {
methods: {
checkDevice() {
if (!this.$store.state.deviceList.includes(this.$route.params.device)) {
this.$router.push({
name: 'NotFound'
})
}
}
},
created() {
this.checkDevice()
}
}
</script>

Vue 3 dynamic components at router level

Dynamic imports is needed for me, eg. i have 10 layouts, but user only visited 3 layouts, I should not import all of the layouts, since its consumed unnecessary resources.
Since its dynamic import, each time i switch between Login & Register path <RouterLink :to"{name: 'Login'}" /> & <RouterLink :to"{name: 'Register'}" />, I got rerender or dynamic import the layout again.
My question is what is the better way to handle it, without rerender or dynamic import the layout again? Or can I save the dynamic import component into the current vue 3 context?
App.vue this is my app with watching the router and switch the layout based on route.meta.layout
<template>
<component :is="layout.component" />
</template>
<script>
import DefaultLayout from "./layout/default.vue";
import {
ref,
shallowRef,
reactive,
shallowReactive,
watch,
defineAsyncComponent,
} from "vue";
import { useRoute } from "vue-router";
export default {
name: "App",
setup(props, context) {
const layout = shallowRef(DefaultLayout);
const route = useRoute();
watch(
() => route.meta,
async (meta) => {
if (meta.layout) {
layout = defineAsyncComponent(() =>
import(`./layout/${meta.layout}.vue`)
);
} else {
layout = DefaultLayout;
}
},
{ immediate: true }
);
return { layout };
},
};
</script>
router/index.js this is my router with layout meta
import { createRouter, createWebHistory } from "vue-router";
import Home from "#/views/Home.vue";
import NotFound from "#/views/NotFound.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/login",
name: "Login",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "login" */ "../views/Login.vue");
},
},
{
path: "/register",
name: "Register",
meta: {
layout: "empty",
},
component: function () {
return import(/* webpackChunkName: "register" */ "../views/Register.vue");
},
},
{ path: "/:pathMatch(.*)", component: NotFound },
];
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_GITLAB_BASE_PATH),
routes,
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 };
},
});
export default router;
You could use AsyncComponent inside the components option and just use a computed property that returns the current layout, this will load only the current layout without the other ones :
components: {
layout1: defineAsyncComponent(() => import('./components/Layout1.vue')),
layout2: defineAsyncComponent(() => import('./components/Layout2.vue')),
},
Had this issue and Thorsten Lünborg of the Vue core team helped me out.
add the v-if condition and that should resolve it.
<component v-if="layout.name === $route.meta.layout" :is="layout">

Accessing to store in the router

I would like to check in my Vuex store whether a user has the 'admin' role before entering the /dashboard route. But I can't properly access data from store.getters.
I use Quasar (Vue.js) and Vuex + Typescript.
In the routes.ts file, on the beforeEnter() function, I can access getters from the store with a console.log(store.myStore.getters). Here I see userInfos inside:
I don't understand why I only get {} and not {...} (Note that if I click on it, I see its contents).
But if I call console.log(store.myStore.getters.userInfos), I don't see the data:
Here is index.ts (router):
import { route } from 'quasar/wrappers'
import VueRouter from 'vue-router'
import { Store } from 'vuex'
import { StateInterface } from '../store'
import routes from './routes'
export default route<Store<StateInterface>>(function ({ Vue }) {
Vue.use(VueRouter)
const Router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
})
return Router
})
Here is routes.ts (router):
import { RouteConfig } from 'vue-router'
const routes: RouteConfig[] = [
{
path: '/',
component: () => import('layouts/Login.vue'),
children: [
{ path: '', component: () => import('pages/Index.vue') },
{ path: '/inscription', component: () => import('pages/SignUp.vue') },
{ path: '/connexion', component: () => import('pages/SignInPage.vue') }
]
},
{
path: '/main',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/Index.vue') },
{ path: '/dashboard', component: () => import('pages/DashboardB2B.vue'),
beforeEnter: (to, from, next) => {
const store = require('../store')
console.log("before enter")
console.log(store.myStore.getters)
return next();
}, },
{
path: '/ajouter-un-referentiel',
component: () => import('pages/ReferentielMetier.vue')
},
{
path: '/init',
component: () => import('components/bot/BotSkeleton.vue')
}
]
},
{
path: '/bot',
component: () => import('layouts/Bot.vue'),
children: [
{
path: '/ajouter-un-referentiel',
component: () => import('pages/ReferentielMetier.vue')
},
{
path: '/init',
component: () => import('components/bot/BotSkeleton.vue')
}
]
},
// Always leave this as last one,
// but you can also remove it
{
path: '*',
component: () => import('pages/Error404.vue')
}
]
export default routes
And here is index.ts with the store (Vuex):
import Vue from 'vue'
import { store } from 'quasar/wrappers'
import Vuex from 'vuex'
Vue.use(Vuex)
import matching from './modules/matching'
import orga from './modules/organigrame'
import user from './modules/user'
export interface StateInterface {
example: unknown
}
let myStore: any
export default store(function({ Vue }) {
Vue.use(Vuex)
const Store = new Vuex.Store<StateInterface>({
modules: {
matching,
orga,
user
},
// enable strict mode (adds overhead!)
// for dev mode only
strict: !!process.env.DEBUGGING
})
myStore = Store
return Store
})
export {myStore}
EDIT: Looks like my console.log runs before the getters are loaded, because when I check out Vue developer tools, I see everything. How can I check the store if the store itself doesn't load before the beforeEnter function?
please try like this
store.myStore.getters["userInfos"]
I'm having exact issue, even if i use async/await :S
try this
router.beforeEach(async(to, from, next) => {
const userInfo = await store.getters.userInfos;
console.log(userInfo);
});

Redirect to specific url in case of wrong url in vuejs

I have two separate routing files where I am importing the component and defining their routing in each of its file and using it in index.js file. Here are my files code:
//router1.js
import Layout1 from 'Layouts/Panel.vue';
const Users = () => import('Views/Users.vue');
const Reports = () => import('Views/Reports.vue');
export default {
path: '/layout1',
component: Layout1,
redirect:'/layout1/reports',
children:[
{
path: 'reports',
component: Reports,
name:'Reports'
},
{
path: 'users',
component: Users,
name:'Users'
}
]
}
//router2.js
import Layout2 from 'Layout/Panel2';
const Demo1 = () => import('Views/Demo1');
const Demo2 = () => import('Views/Demo2');
export default {
path: '/',
component: Layout2,
redirect:'/demo1',
children:[
{
path: '/demo1',
component: Demo1
},
{
path: '/demo2',
component: Demo2
}
]
}
// index.js
import Vue from 'vue'
import Router from 'vue-router'
import router1 from './router1';
import router2 from './router2';
const NotFound = () => import('Views/NotFound.vue');
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
router1,
router2,
{
path: '*',
component: NotFound,
name:'NotFound',
},
]
})
Now, I want to redirect to specific url i.e "not-found" in case of wrong URL. In "NotFound" component I am adding below line of code in mounted lifecycle hook which redirects to URL "not-found".
this.$router.replace({ path: 'not-found' });
But if URL is having parameters or query string it will append to it. For e.g- http://localhost:8080/home/not-found
What I want is that it only shows http://localhost:8080/not-found How should I achieve this. Please help. Thanks!
try this in your mounted function. worked on my side.
this.$router.push({path: '/not-found'})