Recursive slugs in vue-router routes - vue.js

I'm setting up a website which will contain groups, these groups can have sub-groups and these sub-groups can have sub-groups and so on...
Is this in any way possible?
Ofcourse making the path of a route like: /groups/:slug/:slug does not work since it contains duplicate params.
What I was thinking of was using the star pattern as child route, this child route will redirect the user back to the parent route with the slug and a sub-slug, then the next action will be to act on the sub-slug if it exists. But maybe someone has a better solution to this?
const groupRoute = {
path: `/groups/:slug`,
name: 'group',
component: Group,
children: [
{
path: '',
component: GroupsHome
},
{
path: '/subgroups',
component: SubGroups
},
{
path: '*',
beforeEnter(to, from, next) {
next({
name: 'group',
params: {
slug: to.params.slug,
subSlug: to.params.pathMatch
}
})
}
},
]
}

Here is a basic example:
Nested navigation with similar structure for nested objects. The trick here is to use a different param name for inner routes.

Related

Mapping multiple URLs to the same component with Vue Router

In my Vue 2.7.5 app (using Vue Router 3.5.4). I'm trying to map multiple URLs to the same component. Currently, I have a single route mapped to the component:
{
path: '/customer/:customerId',
component: CustomerOrders
}
My goal is to add an optional orderId parameter, such that if a URL like /customer/42/order/59 is accessed, then the same component is loaded, but the order with ID 59 is highlighted (the details of how the param is going to highlight the order are not important).
I tried changing the path to /customer/:customerId/orders/:orderId?, but this would no longer match any URLs of the form /customer/:customerId and would therefore be a breaking change.
My current solution is to use a child route:
{
path: '/customer/:customerId',
component: CustomerOrders,
children: [
{
path: 'order/:orderId',
component: CustomerOrders
}
]
}
This work as the CustomerOrders component is loaded by paths matching either /customer/:customerId or /customer/:customerId/order/:orderId, but it seems like a slightly convoluted approach and I'm not sure it's an appropriate use of child routes.
Is there a better solution?
The easiest way is to register the same component for both routes:
{
path: '/customer/:customerId',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
},
{
path: '/customer/:customerId/order/:orderId',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
},
An exact solution that you are looking for is parsing params manually:
{
path: '/customer/:param+',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
props: router => {
const params = router.params;
const split = params.param.split('/');
params.customerId = split[0];
if (split.length > 2) {
params.orderId = split[2];
}
},
},
Here the :params+ ensures that a customerId and the rest of the route get caught. On the other hand, using :params* catches the /customer route without even a customerId.
CAUTION This approach also /customers/42/...everything...
The vue3 solution is solved here.
EDIT: the following approach cannot catch orderId
Using an alias improves reusability and reduces rendering time but comes with a price of capturing params-change in a watch handler.
{
path: '/customer/:customerId',
name: 'CustomerOrders',
alias: '/customer/:customerId/order/:orderId',
component: () => import( '../views/CustomerOrders.vue'),
},
In this case, your component doesn't get rebuilt by changing routes and also onMount or beforeCreate hooks don't get called either. To catch params-change add a proper watch:
export default {
name: 'CustomerOrders',
watch: {
'$route.params'() {
console.log('params changed. Extract params manually and reload');
},
},
};
This issue is addressed here.

Dynamically registered route is not working when it is just pushed

Hi i'm facing a problem where i want to add new route dynamically
My route structure will look something like this
I'm using "vue-router": "^3.5.3"
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, name:'User'
children: [
{
path: 'profile', component: Profile, name:'Profile'
children: [
{
path: 'about', component: About, name:'About'
children: [
{
path: 'details', component: Details, name: 'Details'
}
]
}
]
}
]
}
]
})
Now my intention is to add child route to Details
this is how my pseudo code looks like
findRouteWithName(routeObj,routeName)
{
// recursive find with any nested level
....
return foundRoute;
}
let routeObj = findRouteWithName(this.$router.options.routes,'Details');
routeObj.children.push(Route object{});
//this.$router.addRoutes([routeObj]) // this line creates problem as mentioned below in **problem:**
Note: if i'm doing like this https://stackoverflow.com/a/48833074/10054910 it is creating multiple nested routes in the url
Problem: page is becoming blank with direct push on children even route does not change in url. with this https://stackoverflow.com/a/48833074/10054910 approach route changes in url but append 2 times
Please help me thanks in advance !!

Vue Router - catch all wildcard not working

I'm using Vue Router with Vue 3 and am trying to add a catch-all route to redirect the user if they try and access an invalid URL. When I try and use the wildcard (*), i get the following error logged to the console:
Uncaught Error: A non-empty path must start with "/"
at tokenizePath (vue-router.esm.js?8c4f:975)
at createRouteRecordMatcher (vue-router.esm.js?8c4f:1106)
at addRoute (vue-router.esm.js?8c4f:1190)
at eval (vue-router.esm.js?8c4f:1335)
at Array.forEach (<anonymous>)
at createRouterMatcher (vue-router.esm.js?8c4f:1335)
at createRouter (vue-router.esm.js?8c4f:2064)
at eval (index.js?a18c:26)
at Module../src/router/index.js (app.js:1402)
at __webpack_require__ (app.js:854)
I'm assuming this is because I don't prepend the path containing the asterisk with a '/', but if I do this then the catch all doesn't work. Here are my routes:
imports...
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/user',
name: 'User',
// route level code-splitting
// this generates a separate chunk (user.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "user" */ '../views/user/User.vue'),
children: [{path: '', component: UserStart}, {path: ':id', component: UserDetail}, {path: ':id/edit', component: UserEdit, name: 'userEdit'}]
},
{path: '/redirect-me', redirect: '/user'},
{path: '*', redirect: '/'}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
The wildcard route is the last object in the routes array. Does anyone know what I'm doing wrong?
Catch all routes (/*) must now be defined using a parameter with a custom regex: /:catchAll(.*)
For example:
{
// path: "*",
path: "/:catchAll(.*)",
name: "NotFound",
component: PageNotFound,
meta: {
requiresAuth: false
}
}
Personally, for Vue 2's * (star or catch all) routes in Vue 3 I use:
{
path: '/:pathMatch(.*)*', <== THIS
name: 'not-found',
component: NotFound
}
Catch all routes (*, /*) must now be defined using a parameter with a custom regex:
The parameter name can be whatever you want like catchAll, pathMatch, noPage etc
{
path: '/:pathMatch(.*)*', //will match everything and put it under `$route.params.pathMatch`
name: 'not-found',
component: NotFound
}
{
path: '/user-:afterUser(.*)',// will match anything starting with `/user-` and put it under `$route.params.afterUser`
component: UserGeneric
}
/:pathMatch(.*)*
The last * it is necessary if you plan on directly navigating to the not-found route using its name.
If you omit it the / character in params, it will be encoded when resolving or pushing.
For example if you use path: /:pathMatch(.*) (note: without the last asterisk) and you go to /user/not-found (a page that doesn't exists) the this.$route.params.pathMatch will be a string => 'user/not-found'
// bad example if using named routes:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
Instead, if you use path: /:pathMatch(.*)* (note: with asterisk) this.$route.params.pathMatch will be an array ['user', 'not-found']
// good example:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
Please read docs: From migration from vue 2 to vue 3 and Catch all / 404 Not found Route

Vue ,listen to route param change but not child route change?

I have the following structure in vue.js ,using vue-router.
routes: [
{
path: '/domains',
component: ListOfDomains
},
{
path: '/domain/:domainName',
component: Domain,
children: [{
// Photos Tab content will be rendered inside domain's <router-view>
// when /domain/:id/photos is matched
path: 'posts',
name: 'posts',
component: PostsTabContent
},
{
path: 'photos',
name: 'photos',
component:PhotosTabContent
}
]
}
]
In my Domain component I use a watcher to fetch domain related data from server such as:
"$route": function(to, from) {
console.log("watcher triggered");
//do update only if domain has changed
//do something , ajax request , update store etc
}
This seems to work fine, but my problem is that I only want to do my updates if the url changes from, say, domain/domain23/posts to domain/domain45/posts and not when it changes from domain/domain23/posts to domain/domain23/photos.
How can I watch only that level in the route for changes?

Unable to understand how vue-router query params work

I am building an app with the following route settings.
export default new Router({
routes: [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/home',
name: 'Dashboard',
component: Dashboard,
beforeEnter(to, from, next) {
if (!store.getters.isLoggedIn) {
next('/login');
} else {
next();
}
}
},
{
path: '/',
redirect: '/home'
}
]
});
I also set query parameters based on some select boxes I have by using $router.push say as follows.
this.$router.replace({
query: {
scope: this.$store.getters.filterScope
}
});
Problems I have
Reloading the page removes the query params from the URL.
Updating one query parameter removes the other one I had. Example - updating scope using the above method removes the dateRange parameter I already had.
I am using Vuex too so is there any way I can manage these query params using the store?
If you store your parameters in vuex and you don't use vuex-persistedstate, when you refresh the page state will be removed.
So, my suggestion for you:
your-awesome-site.com/dashboard?first=hello&second=hi
In your Dashboard component, you can do like this
mounted () {
console.log(this.$route.params)
// Update vuex state filterScope
}
So you will retrieve your parameters from url and save it in vuex.
Also you can solve your second issue using this.$route.params