Dynamically load component as per user role Vue Router - vue.js

I'm trying to load specific components as per the user role in my Vue router.js.
Roles are handled through websanova vue auth and the easiest way to check if a user has a role is by doing Vue.auth.check('rolename')
In my route file, I've tried with something like this:
{
name: 'profile',
path: '/profile',
component: () => Vue.auth.check('secretary')
? import('#/components/secretaries/LayoutProfile')
: import('#/components/doctors/LayoutProfile'),
props: (route) => ({
editMode: false
}),
meta: {
auth: true
}
},
It seems to work for the first time: once the secretary logs in the secretaries/LayoutProfile component is mounted as expected.
But the issue comes if I log out as a secretary and log in as a doctor: the secretaries/LayoutProfile is still being used instead of doctors/LayoutProfile. The only way to fix it is by refreshing the page.
I'm not pretty sure how to deal with this, so far I can just imagine that router.js is loaded on my App mount lifecycle hook, therefore and despite log-in/log-out the router keeps the secretary component mounted.
I just want to be sure that this is not possible before thinking of another approach such as dynamically loading children components within a parent Profile component as per user roles.
FYI, I'm facing no errors at all, just wrong components mounted

Related

Is there any way to have dynamic routes/components in vuejs router?

Hi beautiful Vuejs developers out there!
I have a little problem with routing many Vue components/pages dynamically. In this scenario I am using nested routes to have a couple of routes for my layout components and hundreds of child routes for my pages and as you can imagine I'll have to type many child routes statically or manually, and then add more when I need more child routes in the future code changes but I need a solution to simplify/solve this problem with more efficient/better way like adding those routes from what user types after the layout in the url... here is my example code code:
const routes: RouteRecordRaw[] = [
{
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: 'dashboard',
component: () => import('pages/student/Dashboard.vue'),
},
{
path: 'profile',
component: () => import('pages/student/Profile.vue'),
},
],
},
}
As you see in this code I have a layout named Student and it has two children but I'll have to type manually hundreds of child routes for this layout and other layouts is there any way to dynamically set up those routes with what users enter after the layout name like /student/dashboard or /layout/page and match it with a component name? I mean like params in Angular, can I use the param value itself inside the router to say?
{
path: ':pagename',
component: (pagename) => import('pages/student/' + pagename + '.vue'),
},
let me know if there is an efficient way to solve this problem.
Thanks in advance!
I would, personally, not use this, or advise such an approach, nor have I done it, but this idea came to me when I read your question:
My best guess would be to have a handler component which renders a component dynamically based on a route parameter (say you have /:subpage as a child to /student and the handler component is set to that route), and an exception handler around that to show a 404 page when the user types in an inexistent/unsupported route.
For example, I would dynamically import the component by the route parameter into a predefined let (e.g. let SubpageComponent; outside the try catch block), have a try catch block around the dynamic import assignment for the respective error where catch would set the variable to a 404 page. Then I would add the SubpageComponent into the data() of the component doing the rendering of the route.
Edit
I've written out come code that, maybe, makes sense.
It's based on https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
your routes definition, changed
const routes: RouteRecordRaw[] = [
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: '/:subpage',
component: () => import('pages/student/SubpageRenderer.vue'),
props: true,
},
],
},
]
SubpageRenderer.vue
<script>
export default {
props: ['subpage'],
data() {
return {
currentSubpage: () => import(`./${subpage}.vue`)
}
}
}
</script>
<template>
<component :is="currentSubpage"></component>
</template>
Instead of using the currentSubpage import, you can also use the subpage route prop to bind :is if subpage is the name of a registered component.
Since this would get only "dashboard" from the route, you'd need some namespacing, like "student-dashboard" with the help of template literals. You could make currentSubpage into a template literal that creates the student-${subpage} name.
I'd probably recommend importing the options object of the component designated by the subpage route parameter instead of registering all the components - if you're registering them, you might as well use vue-router the usual way :)
Also, I only think this could work! It should be tested out, and perhaps casing should be kept in mind, and maybe the Layout suffix as well (subpage will probably be all lowercase, and you'll probably have the components named in PascalCase). After uppercasing the first letter, this could also obviously lead to both /student/Dashboard and /student/dashboard being valid routes

How to destroy VueJS component in a method?

I am having a VueJS application with a Tabs component, where user can open different tabs. Also I’m supporting user accounts, so each user can have his own tabs.
But here is the catch: if I’m logged with one user, then I’m logging out and right after that I’m logging in with different user. For a second (or two) after the second user is logged, I’m then able to see the tabs which the previous user has and immediately they are overwritten with the tabs for the second user.
So how I’m able to prevent this to happen? I assume this can be done on in a method when the “log out” button is clicked.
To be more precise, what I’m having is a router and two pages (LoginPage and MainPage) and with logging out I’m redirected with the router to the LoginPage.
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'MainPage',
component: MainPage
},
{
path: '/login',
name: 'LoginPage',
component: LoginPage,
}
]
}
Data in the MainPage comes on it’s create() and mounted() events and I assume that by forcing components to be recreated again will resolve my issue. Since this is not something that I want to save from user to user, instead it should have the effect of loading as when I’m visiting for the first time.
Try assigning a unique key to your MainPage component that's tied to your user's id to force it to re-render when the user changes. Something like ..
<main-page :key="user.id" ...>

Vue.js with VueRouter - how to correctly route on user's refresh

I use Vue and VueRouter (and also Vuex but it is not the case here) in my project. Imagine i have 5 files:
main.js - stores all components definitions, imports them from
external files and so on.
App.vue - it is main component that stores
all other
routes.js - stores all the routing definitions
login.vue -
stores login component (login page)
content.vue - stores page
component
(quite simplified version but you surely get the idea).
Now if i open my path '/' it should reroute me to '/login' page if i am not logged in and to '/content' when i am logged in. Nothing special here.
Now my page works as intended (almost). If I enter in my browser '/content' it tries to render '/content' component with default data (ie userId = -1), then immediately it reroutes me to '/login' page. The '/content' shows just for a second. And it is my problem. I would like to reroute to '/login' without earlier rendering '/content' with default data.
It is obvious that it tries to render '/content' - maybe from cache or something, but since rerouting is my first command in created() it should not
mount /content component in app component, but /login.
Any idea how to prevent it?
I am aware that i do not attach any code, but i hope it wont be necessery for you to understand the problem and advice any solution because it would need cutting and simpliding a lot of code.
In your case, I think you should use vue router's beforeEach hook.
You can use meta field in router to indicates whether the path need authentication, and do processing in beforeEach function.
I will give the sample code.
import Router from 'vue-router';
const router = new Router({
routes: [{
path: '/content',
meta: {
auth: true,
}
}, {
path: '/login',
}]
});
router.beforeEach(async (to, from, next) => {
if (to.matched.some(m => m.meta.auth)) {
// user your authentication function
const isAuth = await getAuthentication;
if (!isAuth) {
next('/login');
}
next();
}
})
if your authentication function is not async function, you should remove async/await keywords
Except if the API in the meantime declares that you are no longer authenticated, the router will not be able to refresh itself by the beforeEach method.
Even with a loop method that retrieves data from the API, which will store them in the store as reactive data.
In Vue everything can be reactive, except Vue router

How to Properly Use Vue Router beforeRouteEnter or Watch to trigger method in Single File Component?

I'm working on an app in Vue.js using Single File Components and Vue Router. I have a Search component where I need to execute a method to re-populate search results each time a user visits the route. The method executes correctly the first time the route is visited because of the "create" hook:
created: function() {
this.initializeSearch();
},
However, when the user leaves the route (to register or log into the app for instance), and returns to the Search page, I can't seem to find a way to automatically trigger this.initializeSearch() on subsequent visits.
Routes are set up in index.js like so:
import Search from './components/Search.vue';
import Login from './components/Login.vue';
import Register from './components/Register.vue';
// Vue Router Setup
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Search },
{ path: '/register', component: Register },
{ path: '/login', component: Login },
{ path: '*', redirect: '/' }
]
export const router = new VueRouter({
routes
})
I gather that I should be using "watch" or "beforeRouteEnter" but I can't seem to get either to work.
I tried using "watch" like so within my Search component:
watch: {
// Call the method again if the route changes
'$route': 'initializeSearch'
}
And I can't seem to find any documentation explaining how to properly use the beforeRouteEnter callback with a single file component (the vue-router documentation isn't very clear).
Any help on this would be much appreciated.
Since you want to re-populate search results each time a user visits the route.
You can use beforeRouteEnter() in your component as below:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component's instance using `vm` .
// this is done because this navigation guard is called before the component is created.
// clear your previously populated search results.
// re-populate search results
vm.initializeSearch();
})
}
You can read more about navigation guards here
Here is the working fiddle

How to get a route param

This is how my router looks like:
export default new VueRouter({
mode: 'history',
routes: [
/**
* Authentication
*/
{ name: 'login',
path: '/',
component: Login,
guest: true
},
{ name: 'registration',
path: '/registreer',
component: Registration,
guest: true
},
/**
* Forum
*/
{ name: 'forum',
path: '/forum',
component: Forum,
auth: true
},
]
});
Now I would like to get the auth value when I go to /forum. How do I get that value? I can't find anything in the docs.
Already tried this:
router.beforeEach((to, from, next) => {
console.log(to.auth);
next()
});
But nothing show up, only undefined.
Any ideas?
This is a good use case for Vuex - https://vuex.vuejs.org/en/intro.html
Vuex allows you to maintain the state of the application, and also store various API responses so that you do not have to reload the same data in other components.
In your specific case, your forum component can check the Vuex state if auth is available (this can be done in created or mounted handler). If auth is not available, then you should immediately re-route to auth page using this.$router.replace("/login")
In your auth component or login component, you can do the authentication normally. Once the user login is successful, you can store the server response in this.$store.state["auth"] - the Vuex state that is available globally for all components. This can be done as follows (as a mutation in Vuex store):
Vue.set(state, "auth", currentUserDetailsObject)
Afterwards, you may redirect to forum component, where auth is available in this.$store.state and therefore forum may proceed to display the component normally.
You may need some ramp-up time to get used to the concepts of actions and mutations, but you will find it very helpful for the rest of your app, once you get used to it.
I think you could find what you want here.
And you probably want to know about Vuex.
Then you can write code like this.
if (!store.getters.auth) {
next({
path: '/login',
query: {redirect: to.fullPath}
})
}