Need help using Vue methods in routed templates - javascript

I'm sure this information is out there somewhere, but I've been Googling all afternoon and haven't been able phrase my question in a way that gets me the understanding I need. I want to write a method in my Vue instance and use it in a template that I display using routing. Here's some partial code:
const Home = { template:
'<div class="container">' +
'I\'m leaving some stuff out, but routing works fine in the real code' +
'</div>'
};
const Level = { template:
'<div class="container">' +
'<h2>Level {{ id }}</h2>' +
'{{ say_hello }}' +
'</div>',
props: ['id','say_hello']
}
// === Define our routes ===
const router = new VueRouter({
routes: [
{ path: '/', component: Home, props: true },
{ path: '/level/:id', component: Level, props: true }
]
})
// === Now fire up Vue and display it on screen ===
const vm = new Vue({
el: '#app',
router,
template: '<router-view></router-view><p><router-link to="/level/1">Level 1</router-link></p>',
methods: {
say_hello: function() {
return 'hello';
}
}
})
I realize that's a little bit of a mess, but in the full code it all works EXCEPT that my '{{ say_hello }}' never works, no matter what I try. I basically always get some variety of "is undefined" or "is not a function" error message. My guess is that the method is not making it past the router for whatever reason, but I haven't been able to figure out what to fix to make it work.
Thanks for your help!
EDIT: I just realized that this is basically a variation of a question I asked a couple of weeks ago, which I never really got an answer to that helped me figure out the problem (several answers, but none that clicked for me). I wound up sidestepping the issue using globals last time. I'd really like to figure out how this actually works instead! Previous question: Vue computed property in router template

You can get vue-router to pass any params offered into component props using props: true.
This way, you can construct your <router-link> elements with any extra data you want. All you have to do is pass in params. This is best facilitated by using a named route, eg
<router-link :to="{ name: 'Level', params: { id: 1, say_hello: 'whatever' } }">
For example
const Home = { template:
`<div class="container">
I'm leaving some stuff out, but routing works fine in the real code
</div>`
};
const Level = { template:
`<div class="container">
<h2>Level {{ id }}</h2>
{{ say_hello }}
</div>`,
props: {
id: [String, Number],
say_hello: {
default: 'Nobody said hello 😦'
}
}
}
// === Define our routes ===
const router = new VueRouter({
routes: [
{ path: '/', name: 'Home', component: Home, props: true },
{ path: '/level/:id', name: 'Level', component: Level, props: true }
]
})
// === Now fire up Vue and display it on screen ===
const vm = new Vue({
el: '#app',
router,
data: () => ({
say_hello: 'hello'
})
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-view></router-view>
<p>
<router-link :to="{ name: 'Level', params: { id: 1, say_hello } }">Level 1</router-link>
</p>
<pre>$route.fullPath = {{ $route.fullPath }}</pre>
</div>

Related

Access Router Params VueJS

I'm creating a blog using Vuejs and I'm fairly new to it.
In short. I have a dynamic list of elements that are loaded onto the screen and when you click on one of the items, I want to go to that page with the rest of the data. I followed somewhat of the same process I did if I was using React.
Router.js
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/post/:id",
name: "PostView",
props: true,
component: PostView
}
]
});
I have my router setup to take dynamic links.
Component That Creates List Data
<template>
<router-link v-bind:to="{ path: 'post/' + post.postId, params: { post } }">
<div> .... </div>
</router-link>
</template>
<script>
import moment from "moment";
export default {
name: "recentPostTile",
props: {
post: Object,
},
};
</script>
As you can see that is how I did my router-link and in my actual Page View this is how I'm trying to read the data. 2 different way's I tried to read it.
props: {
post: Object
},
AND
data() {
return {
postData: this.$route.params.post, (also tried with $route)
};
},
and when I try to console out post/postData I just get undefined. I can assure you that the data does exist since the data is hard coded into the browser.
Also I tried to pass the params as key value pairs as well and that didn't work. I also create a new variable for the data I was going to be passing through the link. That didn't work either. I also haven't tired query, just because I don't want an ugly looking link.
Change your link to:
<router-link :to="{ name: 'PostView', params: { id: post.postId, postData: post } }">
<div> .... </div>
</router-link>
The changes:
Used the name property to specify the route
Removed the hardcoded path
Passed the id param, as that is the param name given in the route definition (/:id)

Vue Router passing props to dynamic routes, unclear documentation

I have a ProjectCard component which, among other things, links to a specific ProjectPage component. The ProjectPage is going to have a bunch of different paragraphs, pictures and so on, not wholly relevant for my question.
How do I pass on props to a ProjectPage? The Vue Docs portion on Boolean Mode is incredibly vague and doesn't really tell me anything useful.
My Router index.js file has the prop decoupling that they mention in the docs
import ProjectPage from "#/views/ProjectPage.vue"
const routes = [
{
path: "projects/:id",
component: ProjectPage,
props: true
}
To test things out, I made the ProjectPage.vue component look like this
<template>
<div>Static text, followed by {{ testString }}</div>
</template>
<script>
export default {
name: "ProjectPage",
props: {
testString: String
}
};
</script>
And I tried passing on a test string to it from my ProjectCard component like this:
<template>
<router-link class="project-link" :to="{ path: `projects/${url}`, params: { testString: 'a dynamic string' } }" exact>
</template>
<script>
export default {
name: "ProjectCard",
props: {
url: String
}
};
</script>
This will correctly take me to the specified route, eg projects/table. However, the {{ testString }} doesn't render and I can only see the Static text, followed by a portion of the component. There's no errors or warnings in the console.
Checking a ProjectCard component in the Vue debugger, the testString exists nested under to/params/testString:
However, on any of the ProjectPage components, testString is undefined
What am I doing wrong here?
what about this
import ProjectPage from "#/views/ProjectPage.vue"
const routes = [
{
name: 'Project',
path: "projects/:id",
component: ProjectPage,
props: true
}
and
<template>
<router-link class="project-link" :to="{ name: `Project`, params: { testString: 'a dynamic string' ,id: url} }" exact>
</template>
As the docs said
params are ignored if a path is provided, which is not the case for query, as shown in the example above.
const userId = '123'
router.push({ name: 'user', params: { userId } }) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// This will NOT work
router.push({ path: '/user', params: { userId } }) // -> /user
The same rules apply for the to property of the router-link component.

Props not updated when set from router

When I set a component props in the route definition the watcher doesn't work and the view is never updated.
In this example the update works correctly :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ nb }}<br><button #click="addOne" type="button">+1</button></div>',
props: {nb: Number},
methods: {
addOne() {
this.nb++
}
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ name: 'test', path: '/test/:nb', component: Test, props: true }
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Note : This example show a "mutating a prop directly" warning, but it's just for the simplicity it's not related to the issue since I don't do that in my code.
In this example the update doesn't work :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: new Counter(route.params.nb)}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
The counter object won't be reactive, so property changes won't be detected. Passing an object as a prop does not add reactivity.
I've just changed one line in your example:
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
Passing the object to Vue.observable will apply the reactivity.
Note that 'applying reactivity' means rewriting all of the properties to use getters and setters. In the simple example presented here that just means the value property, which should be fine. Vue can only rewrite properties it can see, so any data hidden within closures would not be accessible.
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Part of the reason why Vue does not automatically apply reactivity to objects passed as props is that the receiving component shouldn't be modifying the object anyway. It's a violation of one-way data flow. In this example you should probably be updating the route directly rather than modifying the counter object.

How to expose a component's data to child components in a router context? [duplicate]

Suppose I have a Vue.js component like this:
var Bar = Vue.extend({
props: ['my-props'],
template: '<p>This is bar!</p>'
});
And I want to use it when some route in vue-router is matched like this:
router.map({
'/bar': {
component: Bar
}
});
Normally in order to pass 'myProps' to the component I would do something like this:
Vue.component('my-bar', Bar);
and in the html:
<my-bar my-props="hello!"></my-bar>
In this case, the router is drawing automatically the component in the router-view element when the route is matched.
My question is, in this case, how can I pass the the props to the component?
<router-view :some-value-to-pass="localValue"></router-view>
and in your components just add prop:
props: {
someValueToPass: String
},
vue-router will match prop in component
sadly non of the prev solutions actually answers the question so here is a one from quora
basically the part that docs doesn't explain well is
When props is set to true, the route.params will be set as the component props.
so what you actually need when sending the prop through the route is to assign it to the params key ex
this.$router.push({
name: 'Home',
params: {
theme: 'dark'
}
})
so the full example would be
// component
const User = {
props: ['test'],
template: '<div>User {{ test }}</div>'
}
// router
new VueRouter({
routes: [
{
path: '/user',
component: User,
name: 'user',
props: true
}
]
})
// usage
this.$router.push({
name: 'user',
params: {
test: 'hello there' // or anything you want
}
})
In the router,
const router = new VueRouter({
routes: [
{ path: 'YOUR__PATH', component: Bar, props: { authorName: 'Robert' } }
]
})
And inside the <Bar /> component,
var Bar = Vue.extend({
props: ['authorName'],
template: '<p>Hey, {{ authorName }}</p>'
});
This question is old, so I'm not sure if Function mode existed at the time the question was asked, but it can be used to pass only the correct props. It is only called on route changes, but all the Vue reactivity rules apply with whatever you pass if it is reactive data already.
// Router config:
components: {
default: Component0,
named1: Component1
},
props: {
default: (route) => {
// <router-view :prop1="$store.importantCollection"/>
return {
prop1: store.importantCollection
}
},
named1: function(route) {
// <router-view :anotherProp="$store.otherData"/>
return {
anotherProp: store.otherData
}
},
}
Note that this only works if your prop function is scoped so it can see the data you want to pass. The route argument provides no references to the Vue instance, Vuex, or VueRouter. Also, the named1 example demonstrates that this is not bound to any instance either. This appears to be by design, so the state is only defined by the URL. Because of these issues, it could be better to use named views that receive the correct props in the markup and let the router toggle them.
// Router config:
components:
{
default: Component0,
named1: Component1
}
<!-- Markup -->
<router-view name="default" :prop1="$store.importantCollection"/>
<router-view name="named1" :anotherProp="$store.otherData"/>
With this approach, your markup declares the intent of which views are possible and sets them up, but the router decides which ones to activate.
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true }
// for routes with named views, you have to define the props option for each named view:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
Object mode
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
]
})
That is the official answer.
link
Use:
this.$route.MY_PROP
to get a route prop

Passing dynamic data to Vue params/routes

I'm fairly new to Vue and am struggling to get something to work. Not entirely sure if this is possible but I'll ask and we'll see what the Stack Overflow gods have to conjure.
I wanted to know if it is possible to store component data/props for lots of IDs inside the data () portion of the default export.
So the {{$route.params.id}} manages to capture the id from the end of the url, but I want to know whether I can then have the View return other data stored somewhere in a component. So essentially is it possible for me to store data for let's say 5 different IDs all inside the Project.Vue file, or do I simply have to make 5 different files (Project1.Vue, Project2.Vue etc) and then set them all up as separate routes?
So far I have tried adding addings bits to the data () element such as
data () {
return {
msg: 'Projects',
id: [{ 1: 'hi'}, {2: 'hey'}],
two: 'project two',
three: 'project three',
}
}
And then referencing id inside the <template> but that didn't work as it simply returned the whole object. I also tried decoupling as mentioned here: https://router.vuejs.org/en/essentials/passing-props.html but had no joy with that either.
Apologies for my poor explanation but I hope somebody can help to shed light on whether this is possible. Code used below:
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/components/Home'
import Contact from '#/components/Contact'
import About from '#/components/About'
import Projects from '#/components/Projects'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/contact',
name: 'Contact',
component: Contact
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/projects',
name: 'Projects',
component: Projects
},
{
path: '/projects/:id',
name: 'Projects',
component: Projects,
props: {default: true}
},
]
})
Projects.Vue
<template>
<div id="projects">
<h1>{{ header }} {{ $route.params.id }}</h1>
{{id}}
</div>
</template>
<script>
export default {
name: 'Projects',
watch: {
'$route'(to, from) {
// react to route changes...
}
},
data () {
return {
header: 'Projects',
}
}
}
</script>
<style scoped>
</style>
I have managed to figure it out.
In order to dynamically pass data based on the id passed in to the url, you need to create a data object and then inside of the of the <template>, you can pass in the object you have created but then pass the $route.params.id inside of the square brackets. However, it's worth noting that because the object created inside of your data() will use the zero index, it is worth adding a -1 inside of the template. See the below code to understand how it all works:
<template>
<div id="projects">
<h1>{{ msg }} {{ projects[$route.params.id - 1] }}</h1>
</div>
</template>
<script>
export default {
name: 'Projects',
watch: {
'$route'(to, from) {
// react to route changes...
}
},
data () {
return {
projects: [
{ id: 1,
name: 'Project numero uno'
},
{ id: 2,
name: 'Project secundo'
},
{ id: 3,
name: 'Project three'
},
]
}
}
}
</script>
<style scoped>
</style>

Categories