nested router with lazy load not work propertly - vuejs2

I am new to Vue and want to navigate Product child routes, but it do not work & get NotFound page instead.
So my question is how to make it properly.
Or can someone give some details. Thanks
Online Editor
index.js
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/products',
component: () => import('../views/ProductPage.vue'),
children: productRouter
},
{
path: '/**',
component: NotFound
}
]
product.js
const productRouter = [
{
path: '',
name: 'products',
component: ProductPage
},
{
path: 'product/:id',
name: 'ProductDetails',
component: ProductDetails
},
{
path: '**',
component: NotFound
}
]

There are some little mistakes leading to your unexpected result.
In product.js router you should not prefix again with products since it is already in the scope of the /products route. The NotFound route is also not needed in this definition since the parent's NotFound already matches the same route patterns. You can rewrite the product router definition like below :
const productRouter = [
{
path: ':id',
name: 'ProductDetails',
component: ProductDetails
}
]
Then, in ProductList.vue, you should rewrite your router-link as below :
<router-link :to="`/products/${item.id}`"> {{ item.description }} </router-link>
Finally, in ProductPage.vue, you are missing the <router-view></router-view> needed to render your child routes as explained in the vue router documentation.. You could rewrite it like below :
<template>
<div id="productPage">
<h1>This is an Product Page</h1>
<ProductList :items="products"> </ProductList>
<router-view></router-view>
</div>
</template>

Related

Router Vue is not transfering from.params to route

so i have sth like this:
in router:
{
path: '/feature',
name: 'CustomFeature',
component: CustomFeature,
pathToRegexpOptions: { strict: true },
meta: {
title: Custom features,
scrollToTop: true,
}
}
in product component (template):
<a #click="createNewFeature">go</a>
and then:
createNewFeature() {
this.$router.push('/feature#new')
}
and in my CustomFeature component:
beforeRouteEnter(to, from, next) {
console.log(from) // - here is problem
...
it should show me that is going from "product site" route
i tried changing
<a #click="createNewFeature">go</a>
to just
<router-link to="/feature#new">go</router-link>
or
<router-link :to="{ path: '/feature#new' }">go</router-link>
but it doesnt work i tried to add children to '/feature' path in router but i dont know how to do it properly (and if it is a case)

VueJS: How to make a nested router-view always render one of its routes?

TL:DR;Is it possible to make the router-view display a component without being on that component route ?
I am trying to imitate a carousel effect using router-view inside a child component.
The problem is that if I don't click on a router-link the router-view displays nothing.
I want to make on of the router-link be active when no other is in order to force the router-view to always display something.
App.vue with the top router-view:
<template>
<div id="app">
<router-view />
</div>
</template>
Router index.js:
const routes = [
{
path: "/",
name: "LandingPage",
component: LandingPage,
children: [
{
path: "/icons",
name: "Icons",
component: () => import(/* webpackChunkName: "about" */ "../components/portfolio/Icons.vue"),
},
],
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
scrollBehavior(to, savedPosition) {
if (to.hash) {
return {
selector: to.hash,
behavior: "smooth",
// offset: { x: 0, y: 75 }
};
} else {
return savedPosition;
}
},
});
LandingPage.vue:
<template>
<div class="page">
<Home></Home>
<About></About>
<Portfolio></Portfolio>
<Contact></Contact>
</div>
</template>
Portfolio.vue with the second router-view:
<template>
<section id="portfolio">
<ul>
<li v-for="slide in slider" :key="slide.path">
<router-link :to="`/${slide.link}`">{{ slide.text }}</router-link>
</li>
</ul>
<router-view />
</section>
</template>
As you can see I have only one route, which is / for the top router-view. This will render LandingPage.
I use hashes to navigate to the components inside LandingPage like so: <router-link :to="{ path: '/', hash: #${link.path} }">
I am trying to make the router-link :to="/icons" active and the Icons component render inside Portfolio's router-view when no other link from Portfolio is active.
It's important for it to remain active only inside Portfolio, because I have a Navbar with other router-link which go to various hashes inside LandingPage.
Is this even possible ?
If I understood, you want to show Icon when the route is '/'.
To define a default sub-route you need to have a route with an empty value (path:'').
Now if you don't want to change path use the 'alias' mechanism.
const routes = [
{
path: "/",
name: "LandingPage",
component: LandingPage,
children: [
{
path: "/icons",
alias: '',
name: "Icons",
component: () => import(/* webpackChunkName: "about" */ "../components/portfolio/Icons.vue"),
},
],
},
];
If alias doesn't meet your needs, define your sub-route twice(One by empty path second by '/icons')
You can also define /icons as the main route:
const routes = [
{
path: "/icons",
name: "LandingPage",
component: LandingPage,
children: [
{
path: "",
name: "Icons",
component: () => import(/* webpackChunkName: "about" */ "../components/portfolio/Icons.vue"),
},
],
},
];

Dynamic router link changes *slash* to %2F

I've got little problem with dynamic router link. I got array of objects(pages) from API, and one of them is my home:
{
name:"dynamic"
parent_id:0
partners:null
slug:"/"
}
then using v-for I want create router-link like this:
<div v-for="page in pages">
<router-link
:to="{ name: page.name, params: { slug: page.slug }}"
class="v-list__link"
>
</div>
Problem is when I render page this link to home is not <a href="/"> as I expected but it is with endocing reference: %2F => <a href="%2F">
router.js
export default new Router({
scrollBehavior (to, from) {
return { x: 0, y: 0 }
},
mode: 'history',
routes: [
{
path: '/:slug',
name: 'dynamic',
component: Dynamic
},
{
path: '/',
name: 'dynamic',
component: Dynamic
},
{
path: '/contact',
name: 'contact',
component: Contact
}
]
})
does anyone know how to solve it ?
The route's path is /:slug. When resolved with slug equal to / then you get // as the final path, except it will be resolved to /%2F since the params will be encoded with encodeURIComponent.
Remove the leading slash from the slug param:
page.slug.replace(/^\//, '')
You also have two routes with the same name, this isn't allowed. The second dynamic route cannot be resolved by name.

Update the parent data when user navigates to a specific route path

I'm new in VueJs, trying to set up a web application with Vue-route, and want to update the <header> style when user navigates to a specific URL, whether using "URL bar" directly or "navigation bar". In this case, we have a parent component that contains height_status data and some <router-links> on template.
I've done the "navigation bar" part with $emit technique and it works well but then I've tried to use it on created lifecycle hook in order to update the header whenever the /home route is created but event listener will not reach the parent_component.
How can I solve this? Is there a better way to do that?
Please see the code below:
Parent_component.vue
<template>
<div id="app">
<router-link to="/home" #height_size_ctrl="change_height">Home</router-link>
<router-link to="/about">About us</router-link>
<router-link to="/contact">Contact us</router-link>
<header :class="height_status ? 'head-h-s' : 'head-h-m'"></header>
<router-view/>
</div>
</template>
<script>
export default {
name: "Parent_component"
},
data() {
return {
height_status: false
}
},
methods: {
change_height(h) {
this.height_status = h
}
}
}
</script>
router.js
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: about
},
{
path: '/contact',
name: 'contact',
component: contact
}
]
})
home.vue
<template>
<h1>hello</h1>
</template>
<script>
export default {
name: 'home',
created: function(){
return this.$emit("height_size_ctrl", true)
}
}
</script>
You could also change the router:
router.js
{
path: '/home',
name: 'home',
component: Home,
meta: {
headerClass: 'head-h-s'
}
}
In your component
Parent_component.vue
computed: {
headerClass() {
return this.$route.meta.headerClass
}
}
Now headerClass is available in the template.
<header :class="headerClass"></header>
why don't you try class binding on route or route name something like:
<div :class="{'height_status': this.$route == '/home'}">Header</div>
or
<div :class="{'height_status': this.$route.name == 'Home'}">Header</div>
As #kcsujeet said, class binding is the good way we can do this. In this case we need to look at the condition this.$route.path. if value is equal to the /home select 'head-h-m, otherwise select .head-h-s.
<header class="head-sec" :class=" this.$route.path == '/home' ? 'head-h-m' : 'head-h-s'">
Also we're able to access other route defined properties using this.$route. I suggest take a look at the router.js file.
routes: [
{
path: '/home',
name: 'home',
component: Home
}

Vue Router moves to route but loads wrong component

I have the following router config:
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'notselected',
component: PackageUnselected
},
{
path: '/package/:id',
children: [
{ path: 'meta', name: 'packageMeta', component: ViewPackageMeta },
{ path: 'readme', name: 'packageReadme', component: PackageReadme },
{ path: 'docs', name: 'packageDocs', component: PackageDocs },
{
path: 'playground',
name: 'packagePlayground',
component: PackagePlayground
}
]
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '*',
redirect: '/'
}
]
});
And when I'm at the root route it correctly identifies the route name as notselected. When I route to any of the "/package/[id]" routes though it continues to load the PackageUnselected component instead of the appropriate route (aka, ViewPackageMeta, PackageDocs, etc.).
Now at the point in the DOM where I want the route to insert the route's component I have the following template:
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact>
item: {{item}}
<router-view :selectedPackage="selected"></router-view>
</v-tab-item>
And because I have installed vuex-router-sync it's easy to see the route state at any given time. So when clicking on the route that should load PackageDocs:
But the component view window of vue-devtools looks like this:
the highlighted area shows that NO component has been loaded into the tabs. I then tried adding a component to the definition of the parent route /package/:id:
{
path: '/package/:id',
component: Packages,
children: [
{ path: 'meta', name: 'packageMeta', component: ViewPackageMeta },
{ path: 'readme', name: 'packageReadme', component: PackageReadme },
{ path: 'docs', name: 'packageDocs', component: PackageDocs },
{
path: 'playground',
name: 'packagePlayground',
component: PackagePlayground
}
]
},
I then had to create the world simplest component for Packages:
<template>
<view-router-view></view-router-view>
</template>
This results in the following:
Hmmm. Can't figure out what to do next. Anyone have any pointers?
When I route to any of the "/package/[id]" routes though it continues
to load the PackageUnselected component instead of the appropriate
route (aka, ViewPackageMeta, PackageDocs, etc.).
That is the correct behavior of the vue-router.
Children can only be loaded when URL paths are:
/package/[id]/meta
/package/[id]/readme
/package/[id]/playground
/package/[id]/docs
Parent path may not have component defined if you have configured redirect option and your user, who opens /package/[id] will be redirected to your default path (could be anything).
Lets move to the next part of your question...
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact>
item: {{item}}
<router-view :selectedPackage="selected"></router-view>
</v-tab-item>
You don't need to create here 4 different <router-view> tags, you just need one where all your children components will display html code.
<v-tab-item v-for="item in tabs" :id="'tab-item-' + item" :key="item" exact></v-tab-item>
<router-view></router-view>
Now you will have only one router-view and it's the default one. When user clicks on any of your tabs you just need to this.$router.push a new path on #click-event in Packages component. That's it.
I have created a simple example (codepen) to demonstrate how this task can be solved:
Vue.use(VueRouter);
// Components
let MetaPackages = {
mounted() { console.log('Mounted MetaPackages...'); },
template: `<div>MetaPackages...</div>`,
};
let DocsPackages = {
mounted() { console.log('Mounted DocsPackages...'); },
template: `<div>DocsPackages...</div>`,
};
let ReadmePackages = {
mounted() { console.log('Mounted ReadmePackages...'); },
template: `<div>ReadmePackages...</div>`,
};
let Packages = {
mounted() { console.log('Mounted Packages... ' + this.$route.path); },
template: '<div>Packages (parent) screen...<br/><router-view></router-view></div>',
};
// Router
const router = new VueRouter({
mode: 'hash',
routes: [
{
path: "/packages/:id",
component: Packages,
children: [
{path:"meta", component: MetaPackages},
{path:"docs", component: DocsPackages},
{path:"readme", component: ReadmePackages}
]
}
]
});
// Vue instance
const vm = new Vue({
el: '#app',
router,
components: {Packages, MetaPackages, DocsPackages, ReadmePackages}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.2/vue-router.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<router-link to="/packages/100/meta">Meta</router-link>
<router-link to="/packages/100/docs">Docs</router-link>
<router-link to="/packages/100/readme">Readme</router-link>
<hr/>
<router-view></router-view>
</div>