How to achieve n levels of nested dynamic routes in Vue.js? - vue.js

I want to achieve n levels of dynamic nested routes in Vue.js, where n is unknown to me.
for eg -
abc.com/ctx-path/component/1/2/...../n
where 1,2,...n are the levels
How can I achieve this with Vue-router?

Behind the scenes vue-router path matching uses path-to-regexp.
So it should be possible to write something like this
{ path: '/ctx-path/component/:id+', component: Component }
or
{ path: '/ctx-path/component/:id*', component: Component }
You could also add path dynamically at run time, but you'll need to have a trigger to add it.
One last option is to have a catch all route and add your own logic.

This is from docs:
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts
}
]
}
]
})
see link https://router.vuejs.org/guide/essentials/nested-routes.html

Double dynamic nested routes to filter a single view by the nested URL params
const routes = [
{
path: '/category/:categoryId',
name: 'category',
component: () =>
import(/* webpackChunkName: "product" */ '../views/Categories.vue'),
props: (route: Route) => ({
categoryId: route.params.categoryId,
}),
},
{
path: '/category/:categoryId/:attributeIdsJsonString',
name: 'attributeFilter',
component: () =>
import(/* webpackChunkName: "product" */ '../views/Categories.vue'),
props: (route: Route) => ({
categoryId: route.params.categoryId,
attributeIdsJsonString: route.params.attributeIdsJsonString,
}),
},
];
const router = new VueRouter({
routes,
});
Using different route names like this will mean that beforeRouteUpdate won't fire in some instances, so use beforeRouteEnter as well

Related

Default vue-cli lazy load code for route does not work when defined as lambda

I used vue-cli to scaffold a new Typescript project and specified it should include vue-router.
This auto-generated a router/index.ts with a router configuration that looked like this:
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: () => { import(/* webpackChunkName: "about" */ '../views/About.vue') },
}
]
This file compiles fine as one would expect. However, when I try to call the route using <router-link> the About page does not render. I can confirm the route is being called, because if I add a console.log('x') into the import lambda above, I see the 'x' in the console but the About component constructor is never called and the About content is not rendered.
However, if I adjust the index.ts as follows (as per docs), then the route works correctly and displays the view:
const AboutRoute = () => import('../views/About.vue')
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: AboutRoute,
}
]
Can anyone explain why the second version works, but not the first as they seem equivalent to me.
Thanks
Ian
Your 1st solution doesn't work because it's not returning the import.
In Javscript when you add brackets { } to a function, it's not going to return the result automatically, you need to add the return word yourself. So either don't add brackets or add return:
Method 1:
component: () => {
return import(
/* webpackChunkName: "about" */
'../views/About.vue'
)
}
Method 2:
component: () => import(
/* webpackChunkName: "about" */
'../views/About.vue'
)

Dynamic nested routes in Nuxt

I've got to grips with static routes and dynamic routes in Nuxt.
However, I'm trying to work out if it's possible to have effectively unlimited nested pages.
For example, in a standard CMS such as Wordpress I can define a deep nest of pages such as:
*hostname.com/page/other-page/another/yet-another/one-more/final-page*
I suppose I could define an unnecessarily deep page structure, such as:
- /_level1
- index.vue
/_level2
- index.vue
/ _level3
- index.vue
/level4
-index.vue
...and so on. But this doesn't feel particularly efficient or scalable, and introduces lots of duplicate code and maintenance problems.
Is there a better way to achieve this?
You can use nested routes with the "children" option.
https://router.vuejs.org/guide/essentials/nested-routes.html
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts
}
]
}
]
})
You can also import child routes from a separate file.
import UserRoutes from "./users/router.js"
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: UserRoutes
}
]
})
Then in your users/router.js:
export default [
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts
}
]

Why vue router reloads the component, when this component is the same, but the target route is defined in another children level?

Many people have a question: How to reload the component when the route is changed, but the component is the same?.
I have the opposite problem. The component is refreshed, when the "to" route is in different children level then the "from" route.
Vue router 3.0.6.
This is for replacing from route BusinessCaseDetailInsert to route BusinessCaseDetail:
this.$router.replace({ name: 'BusinessCaseDetail', params: { id: newData.BusinessCaseId } });
1) The component is not refreshed, when the "to" and "from" route is in the same level. It is desirable.
var businessCaseDetailRoutes = [
{
path: 'business-case-detail-:id',
name: 'BusinessCaseDetail',
component: () => import('#/components/panels/businesscases/businesscase-detail'),
props: (route) => getBusinessCaseDetailProps(route)
},
{
path: 'business-case-insert',
name: 'BusinessCaseDetailInsert',
component: () => import('#/components/panels/businesscases/businesscase-detail')
}
];
2) The component is refreshed, when the "to" and "from" route is not in the same level. It is not desirable. :-(
var businessCaseDetailRoutes = [
{
path: 'business-case-detail-:idbc',
component: '<router-view></router-view>',
children: [
{
path: '',
name: 'BusinessCaseDetail',
component: () => import('#/components/panels/businesscases/businesscase-detail'),
props: (route) => getBusinessCaseDetailProps(route)
},
{
...
}
]
},
{
path: 'business-case-insert',
name: 'BusinessCaseDetailInsert',
component: () => import('#/components/panels/businesscases/businesscase-detail')
}
];
I need to avoid the refreshing the component, when the target route is defined in different children level.

How to use vue-router to redirect to 404 page with invalid URL parameter

I use vue-router to navigate the user pages by their user id.
And the router.js looks like as follows
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/user/:id',
name: 'user',
component: () =>
import(/* webpackChunkName: "user" */ './views/User.vue'),
props: true
},
{
path: '/404',
name: '404',
component: () => import('./views/404.vue'),
},
]
})
If someone go to the URL of /user/some-invalid-id, how do I redirect it to the 404 page?
In my app, all user data is loaded at the App.js' breforecreate(), and internally the User view is accessed as follows for existing users
<router-link :to="{name: 'user', params:{id: u.pk}}" >
<a> {{u.first_name}} {{u.last_name}} </a>
</router-link>
I know it's possible to redirect programmatically with a push function call. But I don't know where this code should be used.
this.$router.push('/404')
In the User.vue view page, I use a vuex getter called userByID to retrieve data.
userByID: (state) => id => {
return state.users.find(u => (u.pk == id))
}
Should router.push('/404') happen in userByID or its caller? How do we deal with the template rendering with undefined user object?
I think you want to use 'Navigation Guards`, specifically a beforeEnter hook in your /user/:id route.
Something sort of like this (not tested; just directional):
routes: [
{
path: '/user/:id',
name: 'user',
component: () =>
import(/* webpackChunkName: "user" */ './views/User.vue'),
props: true,
beforeEnter: (to, from, next) => {
if (!userById($route.params.id)) {
next('/404');
}
else {
next();
}
}
}
},
{
path: '/404',
name: '404',
component: () => import('./views/404.vue'),
},
]
Note that you'll need to be able to determine if the user is valid without invoking the User.vue component.
You can also implement a beforeRouteEnter hook on User.vue directly, though you'll not be able to call other User.vue methods there as the component won't yet be mounted.
More on navigation guards: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-guards
Given your userById method is accessing the store, I found this post that might help you access the store in your beforeEnter method: How to access async store data in vue-router for usage in beforeEnter hook?

How to Generate Routes for Subcomponents Based on a Base Path

How to get the next route in vue-router
I have the following route: /principal
{path: '/principal', component: Principal}
Now, I need to drive other components that have the same url base,
the new url would be as follows:
/principal/compa
Is it possible to have a single base route be able to display the other components?
Something like this (I know that vue-router does not work like this), but how do you get this behavior?
{
path: '/principal',
component: Principal,
subpath: {
path: 'compa',
component: 'CompA'
}
}
Thanks
There is a children option in VueRouter constructor config to render Vue components with nested routes.
In that particular case, it would be:
{
path: '/principal',
component: Principal,
children: [{
path: 'compa', // it would match /principal/compa
component: CompA
}]
}
From the vue-router doc:
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [ // <-- notice the children property
{
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
path: 'profile',
component: UserProfile
},
{
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
path: 'posts',
component: UserPosts
}
]
}
]
});
Have a look at nested routes for more details.