I'm learning VueJS Routing by reading the docs. I opened an example from the page and added my own nested routes as "/user/foo/posts/1, /user/foo/posts/2, /user/foo/posts/3" and expected them to display properly
I've tried reading the starting example carefully, as well as the documentation for routing (the page on Nested Routes). Nothing is cluing me in on whats wrong.
Here's the code:
HTML:
<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">
<p>
<router-link to="/user/foo">/user/foo</router-link>
<router-link to="/user/foo/profile">/user/foo/profile</router-link>
<router-link to="/user/foo/posts">/user/foo/posts</router-link>
<!-- stuff I added myself below this line (comment is not present in original code) -->
<router-link to="/user/foo/posts/1">/user/foo/posts/1</router-link>
<router-link to="/user/foo/posts/2">/user/foo/posts/2</router-link>
<router-link to="/user/foo/posts/3">/user/foo/posts/3</router-link>
</p>
<router-view></router-view>
</div>
JS:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const UserHome = { template: '<div>Home</div>' }
const UserProfile = { template: '<div>Profile</div>' }
const UserPosts = { template: '<div>Posts</div>' }
const PostOne = { template: '<p>Gonna clone Twitter</p>'}
const PostTwo = { template: '<h5>Not gonna clone IG until Im done Twitter</h5'}
const PostThree = { template: '<p>Gonna be a paid web developer</p>'}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
// UserHome will be rendered inside User's <router-view>
// when /user/:id is matched
{ path: '', component: UserHome },
// 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,
children: [
// the posts within /posts
{ path: "1", component: PostOne },
{ path: "2", component: PostTwo },
{ path: "3", component: PostThree }
]
}
]
}
]
})
const app = new Vue({ router }).$mount('#app')
Specifically, I'm expecting /1 to say "Gonna clone Twitter", /2 to say "Not gonna clone IG until Im done Twitter" (in h5 tags), and /3 to say "Gonna be a paid web developer".
I expected to see those texts loading under "User foo
Posts" when the links are clicked on, and I didn't.
edit: Note that I've only tried running this inside of a JSFiddle environment. here is the fiddle itself where I want the code to run properly: https://jsfiddle.net/rolandmackintosh/use04pwf/9/
So the problem you are facing is that you are not only nesting routes, you are nesting components. When a route has it's own component plus children routes, there needs to be a <router-view> for the child(ren) to load into. This change to UserPosts will make it work for you:
const UserPosts = { template: '<div>Posts<router-view></router-view></div>' }
Related
I would like to grab a child component's "meta" property from parent. Is it possible somehow ?
I know there is a solution with an emit method, but is there some easier way to make it happen ?
// Default.vue <-- parent component
<template>
<h1>{{ pagetitle }}</h1>
<router-view />
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'LayoutDefault',
computed: {
pagetitle () {
let title = this.$route.meta.title // <--- I want to access child's component meta here
// if title not provided, set to empty string
if (!title) title = ''
return title
}
}
})
</script>
// router/routes.js
const routes = [
{
path: '/',
component: () => import('layouts/Default.vue'),
children: [
{
path: 'dashboard',
name: 'dashboard',
meta: { title: 'Dashboard', auth: true, fullscreen: false }, // <--- TAKE THIS
component: () => import('pages/dashboard.vue')
}
]
}
]
// pages/dashboard.vue <-- child component
<template>
<div>
dashboard content
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Dashboard',
meta: { // <--- this should be reachable from the parent component (Default.vue)
title: 'Dashboard',
auth: true,
fullscreen: false
}
})
</script>
You can get component info via $route.matched.
Here's a PoC:
const Dashboard = Vue.defineComponent({
template: "<div>Some dashboard</div>",
meta: { title: "Dashboard" },
})
const router = new VueRouter({
routes: [{ path: "/", component: Dashboard }],
})
const app = new Vue({
router,
computed: {
// Note that this takes the *last* matched component, since there could be a multiple ones
childComponent: (vm) => vm.$route.matched.at(-1).components.default,
},
}).$mount('#app')
<div id="app">
<h1>{{ childComponent.meta.title }}</h1>
<router-view />
</div>
<script src="https://unpkg.com/vue#2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router#3/dist/vue-router.js"></script>
As suggested by Estus Flash in a comment, instead of taking the last matched component we can take the last matched component that has meta defined. To do that, replace the following:
vm.$route.matched.at(-1).components.default
with:
vm.$route.matched.findLast((r) => "meta" in r.components.default)
.components.default
Some approaches I could figure from the web:
Using ref by this.$refs.REF_NAME.$data (As done here: https://stackoverflow.com/a/63872783/16045352)
Vuex or duplicating the logic behind stores (As done here: https://stackoverflow.com/a/40411389/16045352)
Source: VueJS access child component's data from parent
AS title sates, I don't so much need a solution but I don't understand why I'm getting the undesired result;
running v2 vue.js
I have a vue component in a single component file.
Basically the vue should render data (currently being imported from "excerciseModules" this is in JSON format).
IT's dynamic so based on the url path it determines what to pull out of the json and then load it in the page, but the rendering is being done prior to this, and I'm unsure why. I've created other views that conceptually do the samething and they work fine. I dont understand why this is different.
I chose the way so I didn't have to create a ton of routes but could handle the logic in one view component (this one below).
Quesiton is why is the data loading empty (it's loading using the empty "TrainingModules" on first load, and thereafter it loads "old" data.
Example url path is "https...../module1" = page loads empty
NEXT
url path is "https..../module 2" = page loads module 1
NEXT
url path is "https..../module 1" = page loads module 2
//My route
{
path: '/excercises/:type',
name: 'excercises',
props: {
},
component: () => import( /* webpackChunkName: "about" */ '../views/training/Excercises.vue')
}
<template>
<div class="relatedTraining">
<div class="white section">
<div class="row">
<div class="col s12 l3" v-for="(item, index) in trainingModules" :key="index">
<div class="card">
<div class="card-content">
<span class="card-title"> {{ item.title }}</span>
<p>{{ item.excercise }}</p>
</div>
<div class="card-action">
<router-link class="" to="/Grip">Start</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
console.log('script');
let trainingModules; //when initialized this is empty, but I would expect it to not be when the vue is rendered due to the beforeMount() in the component options. What gives?
/* eslint-disable */
let init = (params) => {
console.log('init');
console.log(trainingModules);
trainingModules = excerciseModules[params.type];
//return trainingModules
}
import { getRandom, randomImage } from '../../js/functions';
import { excerciseModules } from '../excercises/excercises_content.js'; //placeholder for JSON
export default {
name: 'excercises',
components: {
},
props: {
},
methods: {
getRandom,
randomImage,
init
},
data() {
return {
trainingModules,
}
},
beforeMount(){
console.log('before mount');
init(this.$route.params);
},
updated(){
console.log('updated');
},
mounted() {
console.log('mounted');
//console.log(trainingModules);
}
}
</script>
I can't tell you why your code is not working because it is an incomplete example but I can walk you through a minimal working example that does what you are trying to accomplish.
The first thing you want to do, is to ensure your vue-router is configured correctly.
export default new Router({
mode: "history",
routes: [
{
path: "/",
component: Hello
},
{
path: "/dynamic/:type",
component: DynamicParam,
props: true
}
]
});
Here I have a route configured that has a dynamic route matching with a parameter, often called a slug, with the name type. By using the : before the slug in the path, I tell vue-router that I want it to be a route parameter. I also set props: true because that enables the slug value to be provided to my DynamicParam component as a prop. This is very convenient.
My DynamicParam component looks like this:
<template>
<div>
<ul>
<li v-for="t in things" :key="t">{{ t }}</li>
</ul>
</div>
</template>
<script>
const collectionOfThings = {
a: ["a1", "a2", "a3"],
b: ["b1", "b2"],
c: [],
};
export default {
props: ["type"],
data() {
return {
things: [],
};
},
watch: {
type: {
handler(t) {
this.things = collectionOfThings[t];
},
immediate: true,
},
},
};
</script>
As you can see, I have a prop that matches the name of the slug available on this component. Whenever the 'slug' in the url changes, so will my prop. In order to react to those changes, I setup a watcher to call some bit of code. This is where you can make your fetch/axios/xhr call to get real data. But since you are temporarily loading data from a JSON file, I'm doing something similar to you here. I assign this data to a data value on the component whenever the watcher detects a change (or the first time because I have immediate: true set.
I created a codesandbox with a working demo of this: https://codesandbox.io/s/vue-routing-example-forked-zesye
PS: You'll find people are more receptive and eager to help when a minimal example question is created to isolate the problematic code. You can read more about that here: https://stackoverflow.com/help/minimal-reproducible-example
I have a body property in data
data(){
return{
body:'Hello im #username1 and #username2'
}
}
I want to convert each #user into a code below, where a user can click that link to go that url path.
<router-link :to="`/${username1}`">#{{username1}}</router-link>
What i tried
<span v-html='bodyReplaced'>
computed: {
bodyReplaced(){
return this.body.replace(
/#\w+/g,
(user) => '<router-link :to="`/${username1}`">#{{username1}}</router-link>'
)
}
}
What the code did:
Convert the string into router-link in the dom but not in the view
I dont know how to replace the # after the match, so i can use it in to="`/${username1}
I think you're looking for something like
<template v-for="part of body.split(/(#\w+)/g)">
<router-link v-if="part[0] == '#'" :to="`/${part.slice(1)}`">{{part}}</router-link>
<template v-else>{{part}}</template>
</template>
new Vue({
el: 'main',
data: {
body:'Hello im #username1 and #username2'
},
router: new VueRouter({
routes: []
}),
})
<main>
<template v-for="part of body.split(/(#\w+)/g)">
<router-link v-if="part[0] == '#'" :to="`/${part.slice(1)}`">{{part}}</router-link>
<template v-else>{{part}}</template>
</template>
</main>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
Create a component called mention and pass the user name as prop, i used the same approach of #Bergi, or you could replaced a computed property in which you replace the mention uder by a element which could be parsed not like router-link :
Vue.component('mention', {
template: `<router-link :to="'/'+user">#{{user}}</router-link>`,
props: ['user']
})
const Foo = {
template: '<div>im foo</div>'
}
const Bar = {
template: '<div>im bar</div>'
}
const routes = [{
path: '/foo',
component: Foo
},
{
path: '/bar',
component: Bar
}
]
const router = new VueRouter({
routes, // short for `routes: routes`,
})
// 4. Create and mount the root instance.
// Make sure to inject the router with the router option to make the
// whole app router-aware.
const app = new Vue({
router,
data() {
return {
body: 'Hello im #foo and #bar'
}
},
computed: {
tokens() {
return this.body.split(' ');
},
bodyReplaced() {
return this.body.split(' ').map(w => {
return w.startsWith('#') ? `${w}` : w;
}).join(' ')
}
}
}).$mount('#app')
<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">
<div>********The first solution********</div>
<template v-for="token in tokens">
<mention v-if="token.startsWith('#')" :user="token.slice(1)"></mention>
<template v-else> {{token}} </template>
</template>
<div>********The second solution********</div>
<span v-html='bodyReplaced'></span>
<router-view></router-view>
</div>
I have some views/components; Home, ScrapeResults, and Search. To help y'all follow what is going on, here are some snippets of code that are relevant to my issue.
Snippet of App.vue
<template>
<v-app-bar>
<Search #selected_card="scrape"/>
</v-app-bar>
<v-content>
<v-container fluid style="width: 80%;">
<router-view :card="card"></router-view> //card prop is in ScrapeResults.vue
</v-container>
</v-content>
</template>
<script>
data() {
return {
card: {}
}
}
methods: {
scrape(card) { //card parameter value is from $emit event from Search
if(this.$router.currentRoute.name != 'scrape-results') {
this.$router.push({name: 'scrape-results', params: { card: card } });
}
else {
this.card = card;
console.log(this.card)
}
}
}
</script>
Snippet of Home.vue
<template>
<div id="home">
<h1>Home</h1>
<Search id="search" #selected_card="scrape"/>
</div>
</template>
<script>
methods: {
scrape(card) { //card parameter value is from $emit event from Search
this.$router.push({ name: 'scrape-results', params: { card: card } });
}
}
</script>
Snippet of ScrapeResults:
<template>
<div id="home">
<h1>Scrape Results</h1>
<p> {{card.name}} - {{card.set_name}} </p>
<v-img :src="card.img_url"></v-img>
</div>
</template>
<script>
...... //irrelevant stuff
props: {
card: Object
},
watch: {
card() {
console.log(this.card)
}
}
</script>
Paths within the app:
/ (Home)
/scrape-results (ScrapeResults)
So how the app work is the user will search for a card (Object) and select from a rendered list. Once the user selects a card from the rendered list, then the user is redirected to the Scrape Results view. All of that works fine when done from Home.vue.
My issue: If the user accessed Scrape Results from Home.vue, and while still on Scrape Results view, if the user were to search for another card via the Search component that is mounted (in the app bar) in App.vue, then the card prop in ScrapeResults.vue doesn't update. The weird thing is that if I manually navigate to Scrape Results by entering /scrape-results in the address bar, search for a card via the Search component in the app bar, then the card prop updates. The card prop will only update if I manually access /scrape-results and then search for a card.
I know all of that sounds confusing, so here's a clip I recorded to that demonstrates what my issue is. Link: https://www.youtube.com/watch?v=aO9qSTa9CCk&feature=youtu.be&hd=1
EDIT: Route definition below
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '#/views/Home'
import ScrapeResults from '#/views/ScrapeResults'
Vue.use(VueRouter);
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/scrape-results',
name: 'scrape-results',
component: ScrapeResults,
props: true
}
]
});
export default router;
So, instead of trying to make passing a prop to router-view work, I just ended up changing the ScrapeResults path in my router.js to /scrape-results/:card_set/:card_name; original was just /scrape-results. I then just pushed extra params in $router.push
this.$router.push({name: 'scrape-results', params: { card: card, card_set: card.set_name, card_name: card.name } });
I am new to Vue and I'm trying to learn how to apply Vue router. I got normal routing to work no problem. When I try to use dynamic routing everything continued to work fine. When I tried to pass props to dynamic routes however my code breaks.
I'm using these cdn versions of Vue and Vue router which are the versions suggested by the official websites:
- https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js
- https://unpkg.com/vue-router#2.0.0/dist/vue-router.js
The HTML
<div id="app">
<h1>{{ message }}</h1>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/user/John">Name</router-link>
</nav>
<!-- route outlet -->
<!-- component matched by route will render here -->
<router-view></router-view>
</div>
The JS
// Route components
const Home = { template: '<div>Home</div>' };
const About = { template: '<div>About</div>' };
const User = { props: ['name'], template: `
<div>
<div>User {{ name }}</div>
<button #click="checkName">Check</button>
</div>`,
methods: {
checkName: function() {
console.log('Params name: ' + this.$route.params.name);
console.log('Props name: ' + this.name);
}
}
};
// Routes for router
const routes = [
{ path: '/', component: Home },
{ path: '/home', redirect: Home },
{ path: '/about', component: About },
{ path: '/user/:name', component: User, props: true }
];
const router = new VueRouter({
routes: routes
});
const app = new Vue({
el: '#app',
data: {
message: 'VueJS Router'
},
router: router
});
When I navigate to the 'Name' page the static text renders fine but the dynamic text fails to load. I added a button that will log the value of name from props and from $route.params to the user. When clicked it turns out that the value of name in props is undefined but the value of name from params is correct. Why is this?
If you're sticking with VueRouter#2.0.0 or lower :
The name that you expect is not passed as a prop but as a route param, cf. Dynamic route matching.
You need to access it from your template as follow : $route.params.name.
You could also use a computed value instead.
If you can update VueRouter
As stated in another answer, and according to the release note of VueRouter#2.2.0, passing down route params as props has only been introduced in v2.2.0, you were using v2.0.0. If you would like to use props you would need to update to (at least) v2.2.0.
CDN link provided on the Vue Router installation page was outdated. Instead of:
https://unpkg.com/vue-router#2.0.0/dist/vue-router.js
use:
https://unpkg.com/vue-router#3.0.1/dist/vue-router.js
Answer provided here:
https://forum.vuejs.org/t/why-is-component-props-undefined-vue-router/34929/5