Dynamically add child routes in Vuejs - vue.js

According to this official example, we have the ability to add nested/children routes in vuejs. But I cannot find any help/docs around a way to add these children routes dynamically. e.g only add child routes when Parent route is visited.
Currently all the routes in a Vue application are defined in a single place where we create Router instance. There is an api called addRoutes, but I don't know how to use that to add lazily loaded features of application along side their routes. If someone is familiar with Angular2+ Module system, that has this ability to define routes for the feature modules inside that module and even make them lazily loaded. Wondering if something could be achieved with VueJs?

You can use $router.addRoutes to re-add a route, specifying children.
You'll need to get the current route definition (as opposed to the $route object) by searching the $router.options.routes array for the route definition object that matches the current $route.path. Then add a children array of route definitions to the object and pass it to $router.addRoutes.
created() {
let { routes } = this.$router.options;
let routeData = routes.find(r => r.path === this.$route.path);
routeData.children = [
{ path: 'bar', component: Bar },
{ path: 'baz', component: Baz },
];
this.$router.addRoutes([routeData])
}
Here's an example fiddle of dynamically adding child routes in the created hook of a route's component definition.

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

VueJS router alias to particular param

I have a lot of articles in my app, and the URL are written like this in Vue Router: /article/:id.
I have particular articles I want to "pin" and have easier URLs. For example: /pinned-article, which should point to /article/3274 and /other-pinned-article, pointing to /article/68173.
I though about adding this to my routes, but it doesn't work:
{ path: '/article/3274', component: Article, alias: '/pinned-article' }
I thought about something else, involving another component:
{ path: '/pinned-article/:id', component: PinnedArticle }
The component PinnedArticle silently aliasing the correct article with a command like router.alias in the <script> section, but it apparently doesn't exist.
Is there a way to solve this problem? I thought I could use some answers I read here in Stackvoverflow (for examples when it comes to redirect /me to /user/:id, but it doesn't apply.
Thanks in advance :)
addRoute
You can achieve this with Dynamic Routing, which is not the same as dynamic route matching, i.e. route params.
(This solution works in both Vue 3 and Vue 2 with Vue Router >= 3.5.0)
By using the addRoute method of Vue router, you can create routes at runtime. You can either use a redirect or not, depending on whether you want the url bar to read /article/3274 or /pinned.
Redirect
If you want the url to change from /pinned to /article/3274, use redirect:
methods: {
pinRoute() {
this.$router.addRoute({
path: '/pinned',
name: 'pinned',
redirect: { name: 'article', params: { id: 3274 }}
})
}
}
Access the route like:
this.$router.push('/pinned')
The above example assumes you give your Article route a name: 'article' property so you can redirect to it
Alias
You can keep the URL as /pinned using alias. Normally the alias would go on the existing Article route definition, but that doesn't work well with route params. You can use a "reverse alias" with a new route:
methods: {
pinRoute() {
this.$router.addRoute({
path: '/params/3274',
name: 'pinned',
alias: '/pinned',
component: () => import('#/views/Article.vue') // Article component path
})
}
}
Access the route like:
this.$router.push('/pinned')
Notes:
You'll probably want to pass an id argument to the pinRoute methods rather than hardcode them like in the examples above.
A nice thing about addRoute with either method above is if the route already exists, say, from the last time you called the method, it gets overwritten. So you can use the method as many times as you like to keep changing the destination of /pinned. (The docs in both Vue 2 and Vue 3 say the route definition will get overwritten, though Vue 2 router throws a duplicate route warning.)
Of course the pinned route won't automatically persist between app refreshes, so you'll need to save/load the pinned id (i.e. using localStorage, etc.) and run one of these methods on app load if you want that

Nuxt.js router doesn't seem to load the correct component

We are trying out Nuxt.js for an app am having a bit of a problem getting their router to load the correct component. I have structured our directory to generate the following:
path: "/articles/:id?",
component: _241eccb7,
name: "articles-id",
children: [{
path: "edit",
component: _4bdace12,
name: "articles-id-edit"
}]
}, {
The issue is that the articles-id-edit never get invoked. For articles/123, the article-id route is invoked and associated component. For articles/123/edit, the article-id route is invoked and the same component when I'd expect the article-id-edit route to be invoked with its corresponding component.
What am I not understanding? What would be a decent way to debug this (like rake routes in Rails or something). Is there a way I can make more explicit my routes rather than automagically creating?
Is your file structure set up properly? Per the docs:
To define the parent component of a nested route, you need to create a
Vue file with the same name as the directory which contain your
children views.
https://nuxtjs.org/guide/routing#nested-routes

How to access url parameters when configuring child router

I am trying to generate some routes dynamically in a child router basing on a parameter provided within the url.
So I have my main router configured with that route :
mydomain.com/#/page/:page that is loading a module "page"
And in my module page I have a configureRouter function which is supposed to fetch the sections relative to the page specified in the url before to add them in the child router:
public async configureRouter(config, router){
page = ???
sections = wait fetchPageSections(page)
//for each section, add route in child router
}
My problem here is how to retrieve the :page parameter, since this will be available only (if I understand well) in the activate() function, that will be called after configureRouter(). However, since that part of the route has already been "matched" in the parent router, I think there should be a way to retrieve it in the configureRouter function.
Thanks.
I don't know if there is a way to retrieve the :page parameter in the configureRouter() method, because as far as I know, at this point the new route has not been triggered yet.
You can retrieve the :page parameter in the created() method.
created() {
let page = this.router.parentInstruction.params.page;
// do something and add routes
//this.router.addRoute({ route: "test", moduleId: "test", nav: true, title: "test" });
//this.router.refreshNavigation();
}
A second option would be using a global object to hold the desired parameter. You could inject this object in the constructor() method of the view component. However, this would be an overkill and unsafe to use. I do not think it is a good idea.
A third option, and the easiest one in my opinion, is using mapUnknownRoutes() for dynamic routes:
router.configure(config => {
config.mapUnknownRoutes(instruction => {
//read the instruction.fragment to get
//you can return a promise and make async requests
return instruction;
});
});
I hope this helps!
The router you are passed in configureRouter(config, router) is the child router. Luckily, it has a parent property that will provide you with what you want.
router.parent.currentInstruction.params.page should give you what you're looking for: the page parameter from the parent router.

Access route params within route/ subRoutes object in Vuejs

Can anybody tell me if there is a way to access the route params directly inside the route objects?, I need to require different components based on the route object.
router.map({
'/dashboard/*': {
component: Home
},
'/dashboard/:module': {
component: Module,
subRoutes: {
'/': {
component: require('./components/'+this.$route.params.module+'.vue')
},
'/:tab': {
component: require('./components/'+this.$route.params.tab+'.vue')
}
}
}
});
You can't require one or another component depending on the route parameters directly in the router configuration. What you can do instead is create a wrapper component that will use a component or another depending on the route parameters, as the route parameters are accessible from any components.
As written in the vue-router documentation:
The route object will be injected into every component in a vue-router-enabled app as this.$route, and will be updated whenever a route transition is performed.
Every of your components have access to the current route through this.$route therefore you could do something like this:
<template>
<component-a v-if="$route.params.param1 === 'a'></component-a>
<component-b v-if="$route.params.param1 === 'b'></component-b>
</template>
I haven't seen the use of $route.params inside the route objects. What you can do though, in your vue you can dynamically load different components based on your $route.params
<component-a v-if="$route.params == 'a'"></component-a>