Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue router - how to have multiple components loaded on the same route path based on user role?

I have app where user can login in different roles, eg. seller, buyer and admin. For each user I'd like to show dashboard page on the same path, eg. http://localhost:8080/dashboard However, each user will have different dashboard defined in different vue components, eg. SellerDashboard, BuyerDashboard and AdminDashboard.

So basically, when user opens http://localhost:8080/dashboard vue app should load different component based on the user role (which I store in vuex). Similarly, I'd like to have this for other routes. For example, when user goes to profile page http://localhost:8080/profile app should show different profile component depending on the logged in user.

So I'd like to have the same route for all users roles as opposed to have different route for each user role, eg. I don't want user role to be contained in url like following: http://localhost:8080/admin/profile and http://localhost:8080/seller/profile etc...

How can I implement this scenario with vue router?

I tried using combination of children routes and per-route guard beforeEnter to resolve to a route based on user role. Here is a code sample of that:

in router.js:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import store from '@/store'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home,

    beforeEnter: (to, from, next) => {
      next({ name: store.state.userRole })
    },

    children: [
      {
        path: '',
        name: 'admin',
        component: () => import('@/components/Admin/AdminDashboard')
      },
      {
        path: '',
        name: 'seller',
        component: () => import('@/components/Seller/SellerDashboard')
      },
      {
        path: '',
        name: 'buyer',
        component: () => import('@/components/Buyer/BuyerDashboard')
      }
    ]
  },
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

in store.js:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    userRole: 'seller' // can also be 'buyer' or 'admin'
  }
})

App.vue contains parent router-view for top-level routes, eg. map / to Home component and /about to About component:

<template>
  <router-view/>
</template>

<script>
export default {
  name: 'App',
}
</script>

And Home.vue contains nested router-view for different user's role-based components:

<template>
  <div class="home fill-height" style="background: #ddd;">
    <h1>Home.vue</h1>
    <!-- nested router-view where user specific component should be rendered -->
    <router-view style="background: #eee" />
  </div>
</template>

<script>
export default {
  name: 'home'
}
</script>

But it doesn't work because I get Maximum call stack size exceeded exception in browser console when I call next({ name: store.state.userRole }) in beforeEnter. The exception is:

vue-router.esm.js?8c4f:2079 RangeError: Maximum call stack size exceeded
    at VueRouter.match (vue-router.esm.js?8c4f:2689)
    at HTML5History.transitionTo (vue-router.esm.js?8c4f:2033)
    at HTML5History.push (vue-router.esm.js?8c4f:2365)
    at eval (vue-router.esm.js?8c4f:2135)
    at beforeEnter (index.js?a18c:41)
    at iterator (vue-router.esm.js?8c4f:2120)
    at step (vue-router.esm.js?8c4f:1846)
    at runQueue (vue-router.esm.js?8c4f:1854)
    at HTML5History.confirmTransition (vue-router.esm.js?8c4f:2147)
    at HTML5History.transitionTo (vue-router.esm.js?8c4f:2034)

and thus nothing is rendered.

Is there a way I can solve this?

like image 323
mlst Avatar asked Dec 16 '19 16:12

mlst


People also ask

Can we define multiple router outlets in a component vue?

Instead of having one single outlet in your view, you can have multiple and give each of them a name. A router-view without a name will be given default as its name. A working demo of this example can be found here.

How do you lazy load a component in vue?

To lazy load a component, we use the variable “const” based and attach an arrow function followed with the dynamic import syntax. How do we lazy load components? To apply lazy loading to Vue. js components we need to change the default import of components to variable “ const” based components as shown below.

How do I create a dynamic route vue?

Adding dynamic routes in VueUpdate the router/index. js file with the new route in the routes array. Remember to include the import for the Post component. Restart your app and head to localhost:8080/post/dynamic-routing in your browser.


2 Answers

You might want to try something around this solution:

<template>
  <component :is="compName">
</template>
data: () {
 return {
      role: 'seller' //insert role here - maybe on `created()` or wherever
 }
},
components: {
 seller: () => import('/components/seller'),
 admin: () => import('/components/admin'),
 buyer: () => import('/components/buyer'),
}

Or if you prefer maybe a bit more neat (same result) :

<template>
  <component :is="loadComp">
</template>
data: () => ({compName: 'seller'}),
computed: {
 loadComp () {
  const compName = this.compName
  return () => import(`/components/${compName}`)
 }
}

This will give you the use of dynamic components without having to import all of the cmps up front, but using only the one needed every time.

like image 177
Jesus Avatar answered Sep 22 '22 18:09

Jesus


One approach would be to use a dynamic component. You could have a single child route whose component is also non-specific (e.g. DashboardComponent):

router.js

const routes = [
  {
    path: '/',
    name: 'home',
    children: [
      {
        path: '',
        name: 'dashboard',
        component: () => import('@/components/Dashboard')
      }
    ]
  }
]

components/Dashboard.vue

<template>
  <!-- wherever your component goes in the layout -->
  <component :is="dashboardComponent"></component>
</template>

<script>
import AdminDashboard from '@/components/Admin/AdminDashboard'
import SellerDashboard from '@/components/Seller/SellerDashboard'
import BuyerDashboard from '@/components/Buyer/BuyerDashboard'

const RoleDashboardMapping = {
  admin: AdminDashboard,
  seller: SellerDashboard,
  buyer: BuyerDashboard
}

export default {
  data () {
    return {
      dashboardComponent: RoleDashboardMapping[this.$store.state.userRole]
    }
  }
}
</script>
like image 24
Matt U Avatar answered Sep 21 '22 18:09

Matt U