How to Generate Routes for Subcomponents Based on a Base Path - vue.js

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.

Related

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 achieve n levels of nested dynamic routes in 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

Vuejs Display a router name in Component

How can I display a router name in a component ?
Example:
const routes = [
{ path: '/documents', component: Documents, name:"Documents" ,props:true},
{ path: '/queries', component: Queries, name:"Queries", props:true}
]
I want to display the name property as a title in the component. Is this possible? how?
props:true will convert path parameters to properties:
{ path: '/documents/:name', component: Documents, name:"Documents", props:true},
You can use an object instead of true and then send in a string.
{ path: '/documents', component: Documents, name:"Documents", props:{ name:'Documents'}},
In your component, register the property
props: { name:String }
And then use it in a template like this:
<div>{{name}}</div>
You can also refer to the route name using the components $route object
<div>{{$route.name}}</div>
To specify title to a component you can use router's meta property, Link
const routes = [
{
path: '/documents',
component: Documents,
name:"Documents" ,
props:true,
meta: {
title: 'Documents'
}
},
{
path: '/queries',
component: Queries,
name:"Queries",
props:true,
meta: {
title: 'Queries'
}
}
]
In main.js,
import router from '#/routes'
router.beforeEach((to, from, next) => {
document.title = `Currently at - ${to.meta.title}`
next()
})

Is there a way to remove a directory from a dynamic URL using Vue-Router?

I've build a vue.js web app for an insurance brokerage where every agent has their own website that is generated from their profiles.
This is what the link looks like in my vue-router index file"
{
path: '/agents/:id',
name: 'AgentSite',
component: AgentSite
},
Everything works great EXCEPT that the urls are getting too long to fit on some business cards. I would like to change the URLs to be like this:
{
path: '/:id',
name: 'AgentSite',
component: AgentSite
},
However, then every other bit of dynamic content in the app loads our agent website template (AgentSite). Quotes, Clients, Policies... they won't load properly.
Is there a way to remove the "/agents" from the URLs without messing up the rest of our application? I could shorten it to "/a/:id but that ends up being more confusing than it's worth.
Thanks!
EDIT: a couple of people have mentioned solutions that work when the agent id is a number. That's a great idea except that we have built agent "slugs" to use instead.
On the agent website layout:
created() {
console.log(this.$route.params.id);
this.$store.dispatch("getAgentFromSlug", this.$route.params.id);
}
and in the store:
getAgentFromSlug({commit}, payload){
const db = firebase.database();
db.ref("users/").orderByChild("slug").equalTo(payload).once("value",
(snap) => {
console.log(snap.val());
var info = snap.val();
commit("setAgentSiteInfo", info[Object.keys(info)[0]])
})
}
So, our route Id is really a slug.
Considering ids are numbers, you could use:
{
path: '/:id(\\d+)',
name: 'AgentSite',
component: AgentSite
},
Which only matches if id is made only of numbers.
Update: A couple of people have mentioned solutions that work when the agent id is a number. That's a great idea except that we have built agent "slugs" to use instead.
If the names can conflict with existing routes, declare the agent route last.
From the Matching Priority docs (emphasis mine):
Matching Priority
Sometimes the same URL may be matched by multiple routes. In such a
case the matching priority is determined by the order of route
definition: the earlier a route is defined, the higher priority it
gets.
In other words, declare like:
routes: [
{
path: '/',
component: HomePage
},
{
path: '/quotes',
component: Quotes
},
{
path: '/clients',
component: Clients
},
{
path: '/:id',
component: AgentSite,
props: true
}
]
See CodeSandbox demo Here.
Handling 404 pages
Would I then declare the 404 page route above or below the "AgentSite" in your example? { path: "*", component: PageNotFound }
The AgentSite route would match any URL not matched previously, so you'll have to handle the 404s inside the AgentSite component.
First, declare the 404 route after the AgentSite:
routes: [
// ... (other routes)
{
path: "/:id",
component: AgentSite,
props: true
},
{
path: ":path",
name: "404",
component: p404,
props: true
}
]
Then, inside AgentSite, get the agent :id, check if it is a known agent and, if not, redirect to the 404 route by name (otherwise it would match agent again).
export default {
props: ["id"],
data() {
return {
availableAgents: ["scully", "bond", "nikita"]
};
},
created() {
let isExistingAgent = this.availableAgents.includes(this.id);
if (!isExistingAgent) {
this.$router.push({
name: "404",
params: { path: this.$route.fullPath.substring(1) }
});
}
}
};
The CodeSandbox demo Here already contains this handling.
You can use regex matching if you :id has a specific format (example from vue-router repository).
For example, if your :id is a number:
const routes = [
{ path: '/:id(\\d+)', component: Foo },
{ path: '/bar', component: Bar }
]
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [
{ path: '/:id(\\d+)', component: Foo },
{ path: '/bar', component: Bar }
]
const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
.router-link-active {
color: red;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/321321">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>