Vue Router moves to route but loads wrong component - vue.js

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>

Related

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"),
},
],
},
];

nested router with lazy load not work propertly

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>

vue-router navigate to the same route and re-run mounted hook

How can I naviagte to the current route using router-link and re-run mounted hook?
HTML
<!-- Include the library in the page -->
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-router"></script>
<!-- App -->
<div id="app">
<nav>
<router-link :to="{ name: 'home' }" exact>Home</router-link>
<router-link :to="{ name: 'about' }" #click.native.prevent="router.push({ name: 'about' })">About</router-link>
</nav>
<router-view :key="$route.fullPath"></router-view>
</div>
JS
console.clear()
console.log('Yes! We are using Vue version', Vue.version)
Vue.use(VueRouter)
const Home = {
template: `<h1>Home</h1>`,
}
const About = {
template: `<h1>{{new Date()}}</h1>`,
mounted(){
console.log('mounted')
}
}
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
]
const router = new VueRouter({
routes,
})
// New VueJS instance
var app = new Vue({
// CSS selector of the root DOM element
el: '#app',
// Inject the router into the app
router,
})
In the above example, if I navigate to 'About' it shows the timestamp from new Date and logs 'mounted'. However, if I'm already on /about, clicking the about link does nothing. I want to re-run the whole component lifecycle when clicking 'About', even if I'm already hit it.
You'll need to change the key in the <router-view> element whenever the user clicks your about page, that will force the mounted hook:
<template>
<div id="app">
<router-link #click.native="updateViewKey" :to="{ name: 'about' }">About</router-link>
<router-view :key="viewKey"></router-view>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
viewKey: 1
};
},
methods: {
updateViewKey() {
this.viewKey+=1;
}
}
};
</script>
Try this :
router.push({
name: 'routeName',
params: {
id: 'new value'
}
})

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 is there a way to pass data from route to component?

I am using Vue.js 2.0, and I have this exact same code in 2 differents files, so I decided to build only one component and redirect the 2 routes on it, then I just need to pass the ID to the route, this way my 2 components can display differents resultats.
Note: the only thing that changes is the ID dsgh151rhj12t1j5j I would like that each route can send it own ID to my PageContentfulView component
EDIT: I just want to pass data from Route to the component
<template>
<div>
<div class="container">
<hp-row>
<hp-column>
<component v-for="component in content.column" :data="component" :key="component.id" :is="getComponentIdentifier(component.is)"></component>
</hp-column>
</hp-row>
</div>
</div>
</template>
<script>
import ModularView from '#/views/ModularView'
export default {
name: 'PageContentfulView',
mixins: [ModularView],
created () {
this.fetch('blocks/dsgh151rhj12t1j5j')
},
}
</script>
Routes:
{
path: '/satisfaction-guarantee',
name: 'SatisfactionView',
component: load('PageContentfulView'),
},
{
path: '/about-us',
name: 'AboutUsView',
component: load('PageContentfulView'),
},
'props' can solve your problem.
<template>
<div>
<div class="container">
<hp-row>
<hp-column>
<component v-for="component in content.column" :data="component" :key="component.id" :is="getComponentIdentifier(component.is)"></component>
</hp-column>
</hp-row>
</div>
</div>
</template>
<script>
import ModularView from '#/views/ModularView'
export default {
name: 'PageContentfulView',
mixins: [ModularView],
props: ['id'],
created () {
this.fetch('blocks/' + this.id)
},
}
</script>
route
{
path: '/satisfaction-guarantee',
name: 'SatisfactionView',
component: load('PageContentfulView'),
props: { id: 'dsgh151rhj12t1j5j' },
},
{
path: '/about-us',
name: 'AboutUsView',
component: load('PageContentfulView'),
props: { id: 'blablablabla' },
},
You can do it using Dynamic Route Matching. You just need to include the parameter on the definition of the route, like below:
{
path: '/satisfaction-guarantee/:ID',
name: 'SatisfactionView',
component: load('PageContentfulView'),
}
Inside you component, to access the ID, you just need to use the this.$route.params.ID.
Passing props to route component allows an object mode.
Example from the documentation:
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
Route meta fields
Also you always can use the meta property to pass extra data (like a requireAuth flag)
const router = new VueRouter({
routes: [
{ path: '/test', component: TestComponent, meta: { someBoolean: false } }
]
})
And you can access it within your component
created() {
let meta = this.$route.meta;
}
Set the props in your route to true and place /:id at the end of your path
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter/:id',
component: Promotion,
props: true
}
]
})
You can now extract the param id of your route from the props in the corresponding component
<template>
<div>
<div class="container">
<hp-row>
<hp-column>
<component v-for="component in content.column" :data="component" :key="component.id" :is="getComponentIdentifier(component.is)"></component>
</hp-column>
</hp-row>
</div>
</div>
</template>
<script>
import ModularView from '#/views/ModularView'
export default {
name: 'PageContentfulView',
mixins: [ModularView],
props: ['id'],
created () {
this.fetch('blocks/'+id)
},
}
</script>