VueRouter does not receive meta property in router-link tag - vue.js

I'm trying to pass meta fields via <router-link> tag. Like this:
<router-link :to="{path: '/inlineMeta', meta: {title: 'Inline meta'}}">
Inline meta
</router-link>
I want to achieve this because I can dynamically pass metas from a backend framework directly on the tag itself. Declaring routes option in a JS file simply does not have that power.
Theoretically, any object passed via to prop should be pushed to router stack right? But in this case it doesn't seem like so.
If I declare meta in routes option, it definitely works. There is no doubt about that.
I wonder if it's possible to do so, and how would I do that?
A small fiddle to illustrate the problem. Click on URLs and notice the console. I can't get StackOverflow snippet to work properly.
JSFiddle
Vue.use(VueRouter);
const cComponent = {
data() {
return {
fetchedData: null
}
},
template: `<div>The meta is: {{$route.meta.title}}</div>`,
}
const routes = [{
path: "/inlineMeta",
component: cComponent,
},
{
path: "/routeMeta",
meta: {
title: 'Meta declared in routes'
},
component: cComponent,
}
]
const router = new VueRouter({
mode: "history",
routes,
})
router.beforeEach((to, from, next) => {
console.log("The meta title is : " + to.meta.title);
})
const app = new Vue({
el: "#app",
router,
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src=" https://unpkg.com/vue-router#3.5.1/dist/vue-router.js"></script>
<div id="app">
<router-link :to="{path: '/inlineMeta', meta: {title: 'Inline meta'}}">Inline meta</router-link>
<router-link :to="{path: '/routeMeta'}">Meta declared in routes</router-link>
<router-view></router-view>
</div>

If you want to pass the parameters between two components, use props.
v-bind:to in <route-link> is just used for route match, so you can't use it to set the value of meta.
If you want to set the meta without declaring it in route, i think using "this.$route.meta.title="Inline meta" " in js.
like using the click event:
<li #click="setMeta">
<router-link :to="{path: '/inlineMeta'}">
Inline meta
</router-link>
</li>
and in js:
method:{
setMeta(){
this.$route.meta.title="Inline meta";
}
}
you can also use mounted() in the new component you just routed to like :
mounted(){
this.$route.meta.title="Inline meta"
}
and meta will be modified after reloading
This may work though it may looks not that elegant.

Related

Where should route meta data be loaded in a Vue app?

I'm in the process of setting up a VueJs SPA. I'm using vue-router and I'm trying to find the best solution to the following problem. I have a series of routes. Each of which needs to call an API to get the meta data for the given ID.
/industry/:id/overview
/industry/:id/top-stories
/industry/:id/top-tweets
/brand/:id/overview
/brand/:id/top-stories
/brand/:id/top-tweets
I've been looking at using created or beforeRouteEnter/beforeRouteUpdate and I'm a bit lost. Ideally, I would only fetch new data when a new /industry/:id is reached, not when navigating between pages within the same ID. Also, I'd like to avoid having to define the fetch to grab data in every page component. Also don't want to over complicate this, so my question is, Is there a standard method for tackling this issue?
Clarification:
When I say meta here, I mean data returned from an API about the given industry or brand which I pull using the ID in the route. The api call includes the name of the industry/brand which I want to have on page as soon as the page is presented to the user.
I have something similar. I tackle this using the following approach:
I use the same component for all /industry/:id Vue likes to reuse components wherever it can so if two routes (for example /industry/:id/overview and /industry/:id/top-stories) are using the same component it will stay the same.
What does change, however, is the route meta. So if you add a page key to the meta object in the route objects, and probably add a computed property called page that return this.$route.meta.page, you can use v-if attributes to conditionally render any component. So you might have something like <div v-if="page === 'overview'"></div><div v-else-if="page==='top-stories'"></div>
What this allows you to do is fetch all the data from the API during created or mounted lifecycle and store it as the state. Since the route change doesn't reload the component the state stays the same.
Here is a code example
// router.js
const Project = () =>
import(/* webpackChunkName: "projects" */ "./views/projects/_id");
export default new Router({
mode: "history",
routes: [
{
path: "/projects/:project_id/views",
name: "ViewProject",
component: Project,
meta: {
page: "views",
}
},
{
path: "/projects/:project_id/export",
name: "ExportProject",
component: Project,
meta: {
page: "exports"
}
},
{
path: "/projects/:project_id/recommendations",
name: "ProjectRecommendations",
component: Project,
meta: {
page: "recommendations"
}
},
]
});
And here is the template
<template>
<div v-if="project">
<h1>{{ project.name }}</h1>
<router-link :to="/project/someid/views">Views</router-link>
<router-link :to="/project/someid/exports">Exports</router-link>
<router-link :to="/project/someid/recommendations">Recommendations</router-link>
<ul v-if="page==='views">
<li v-for="(view, i) in project.views" :key="i">{{ views }}</div>
</ul>
<ul v-else-if="page==='exports">
<li v-for="(export, i) in project.exports" :key="i">{{ export }}</div>
</ul>
<ul v-else-if="page==='recommendations">
<li v-for="(recommendation, i) in project.recommendations" :key="i">{{ recommendation }}</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
project: null
}
},
computed: {
page() {
return this.$route.meta.page;
}
},
mounted() {
this.getProject()
},
methods: {
getProject() {
axios
.get(`/projects/someid`)
.then(res => this.project = res.data)
}
}
}
</script>

Dynamic Vue Router

I am researching whether a vue router is the best approach for the following scenario:
I have a page containing 'n' number of divs. Each of the divs have different content inside them. When a user clicks on a button in the div, I would like the div to open in a separate browser window (including its contents).
Can a route name/component be created on the fly to route to? Since I have 'n' number of divs, that are created dynamically, I cannot hard-code name/components for each one
<router-link :to="{ name: 'fooRoute'}" target="_blank">
Link Text
</router-link>
I want to avoid the same component instance being used (via route with params) since I may want multiple divs to be open at the same time (each one in their own browser window)
If the link is opening in a separate window, it makes no sense to use a <router-link> component as the application will load from scratch in any case. You can use an anchor element instead and generate the href property dynamically for each div.
To answer your questions:
A route name cannot be created dynamically since all routes must be defined at the beginning, when the app (along with router) is being initialized. That said, you can have a dynamic route and then dynamically generate different paths that will be matched by that route.
There is no way for the same component instance to be reused if it's running in a separate browser window/tab.
It is possible to create dynamic router name.
profileList.vue
<template>
<main>
<b-container>
<b-card
v-for="username in ['a', 'b']"
:key="username"
>
<b-link :to="{ name: profileType + 'Profile', params: { [profileType + 'name']: username }}">Details</b-link>
</b-container>
</main>
</template>
<script>
export default {
name: 'profileList',
data () {
return {
profileType: ''
}
},
watch: {
// Call again the method if the route changes.
'$route': function () {
this.whatPageLoaded()
}
},
created () {
this.whatPageLoaded()
},
methods: {
whatPageLoaded () {
this.profileType = this.$route.path // /user or /place
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
b-container, b-card, b-link are taken from bootstrap-vue, so you can freely change it.
router.js
const router = new Router({
mode: 'hash',
base: process.env.BASE_URL,
linkExactActiveClass: 'active',
routes: [
// USERS
{
path: '/user/:username',
name: userProfile,
component: userProfile
},
{
path: '/user',
name: 'userList',
component: profileList
},
// PLACES
{
path: '/place/:placename',
name: placeProfile,
component: placeProfile
},
{
path: '/place',
name: 'placeList',
component: ProfileList
}
]
})

Passing ID through router-link in Vue.js

I have 2 router links that link to the same page (definition page) but has different ids, in my definition page I have an if else loop that checks the id and then posts the apropriate definition for that id.my problem is that my loop can't properly read my id and goes straight to my else statment, this is the closest that I've gotten it to work.
My 2 router-links in page 1
<router-link :to="{ path: '/Pulse/Definition',id:'Alignment'}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
<router-link :to="{ path: '/Pulse/Definition'}" id="Trust" append><a >Read more ></a></router-link>
My definition page
<template>
<div class="PulseDefinition page row">
<h2 v-if=" id=='Alignment'">hello world {{id}}</h2>
<h2 v-else-if=" id=='Trust'">hello world {{id}}</h2>
<h2 v-else>Sorry try again</h2>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.PulseDefinition{
margin-top:2.5rem;
margin-left:3rem;
background-color: aquamarine;
width:50rem;
height:50rem;
}
</style>
Router
import Vue from 'vue';
import Router from 'vue-router';
import Community from '../components/PulseCommunity';
import Home from '../components/Home';
import Definition from '../components/Definition.vue';
Vue.use(Router)
export default new Router({
routes:[
{
path:'Tuba',
name:'Tuba',
component: Default
},
{
path:'/Pulse',
name:'Pulse',
component:PulseNav,
children:[{
path:'/Pulse/Overview',
name:'Overview',
component:Overview
},
{
path:'/Pulse/Personal',
name:'Personal',
component:Personal
},
{
path:'/Pulse/Community',
name:'Community',
component:Community
},
{
path:'/Pulse/Definition/:id',
name:'Pulse Definition',
component:Definition
}
]
},
{
path:'/Coaching',
name:'Coaching',
component:Coaching
},
{
path:'/Comunication',
name:'Comunication',
component:Comunication
},
{
path:'/Home',
name:'Home',
component:Home
},
]
})
Normally when your using the router inside of a Vue application you'll want to use route parameters, check out the dynamic routing link here.
Using the same example:
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User }
]
})
Here in our router whenever we navigate to a url where /user/ is present providing we then add something after we can match the /:id section of it. Then inside of our component we are able to query the parameters for the ID that was sent in our url:
console.log(this.$route.query.id)
Using this we could then save that value into our component or we could build reactivity around this.$route.query.
In your case you'd only need to append to the string that you pass into that router link by simply using your data / methods or if you require further rules you could use a computed method. This might become or something simmilar:
<router-link :to="{ path: '/Pulse/Definition'+ this.alignmentType}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
i found a solution thx to the help of li x and a senior coworker of mine,here is the awnser.
my working router-link in page 1
<router-link :to="{ path: '/Pulse/Definition/'+'Alignment'}" v-bind:tooltip="Alignment" append><a >Read more ></a></router-link>
im adding the id(Alignment) to my url with[:to="{ path: '/Pulse/Definition/'+'Alignment'}"]
my definition page
<template>
<div class="PulseDefinition page row">
<h2 v-if=" this.$route.params.id=='Alignment'">hello world {{this.$route.params.id}}</h2>
<h2 v-else-if=" this.$route.params.id=='Trust'">hello world {{this.$route.params.id}}</h2>
<h2 v-else-if=" this.$route.params.id=='undefined'">Sorry try again {{this.$route.params.id}}</h2>
<h2 v-else>XXXSorry try againXXX{{this.$route.params.id}}</h2>
<!-- {{console.log("hi")}} -->
</div>
</template>
<script>
// console.log(this.$route.query.id);
export default {
}
</script>
im using [this.$route.params.id] to retrieve my id, and my router page stayed the same.
thank you all for the great help ;)

vue-router doesn't recognize $ref from a component after routing

I'm new with Vuejs and vue-router I've been reading a lot View documentation and forums to figure this out.
I can make my routing working well. But I can't (easily) the "ref" from the content that was routed.
I say "easily" because I found on "this.$children[0].$children[0].$refs" which doesn't look correct to me and also difficult to maintain.
What I would like is to have easy way like "this.$refs" or "this.router["foo"].$refs".
I'll paste the vue documentation basic example with "ref" in the elements.
HTML
<div id="app">
<h1 ref="myrefInitial">Hello App!</h1>
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
<p>
refs founds: {{getAllRefs}}
</p>
</div>
<script src="index.js"></script>
JS
const Foo = {
template: '<div ref="myrefFoo">foo</div>'
}
const Bar = {
template: '<div ref="myrefBar">bar</div>'
}
const routes = [{
path: '/foo',
component: Foo
},
{
path: '/bar',
component: Bar
}
]
const router = new VueRouter({
routes // short for `routes: routes`
})
router.afterEach((to, from) => {
console.log("going to " + to.fullPath)
console.log(to);
router.app.getAllRefs();
})
const app = new Vue({
router,
computed: {
getAllRefs: function(){
return this.$refs
}
}
}).$mount('#app')
JsFiddle example
https://jsfiddle.net/3rdSenna/kaqqsrob/
In the end I wrote a simple For loop to dig on every $refs and find the child $refs name until is matched. It solved my problem.
But I believe the suggestion from #rinatdobr would suite me well.

How to pass data from one view to another with the vue-router

When using the vue-router with .vue files, there is no documented way to pass data from one view/component to another.
Let's take the following setup...
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
let routes = [
{
path: '/page1',
component: require('./views/Posts.vue')
},
{
path: '/page2',
component: require('./views/EditPost.vue')
}
];
let router = new VueRouter({
routes
});
new Vue({
el: '#main',
router
});
Posts.vue:
<template>
<div>
Posts.vue passing the ID to EditPost.vue: {{ postId }}
</div>
</template>
<script>
export default {
data() {
return {
allPostsHere: // Whatever...
}
}
}
</script>
EditPost.vue:
<template>
<div>
EditPost.vue received ID from Posts.vue: {{ receivedId }}
</div>
</template>
<script>
export default {
data() {
return {
receivedId: // This is where I need the ID from Posts.vue
}
}
}
</script>
Please note: It is not possible to receive the ID directly from the EditPost.vue, because it has to be selected from Posts.vue.
Question: How can I pass the ID from one view/component to the other?
A route can only be accessed via a URL and a URL has to be something user can type into the URL bar, therefore to pass a variable from one view component to another you have to use route params.
I assume you have a list of posts in Posts component and want to change page to edit a specific post in EditPost component.
The most basic setup would be to add a link in the post list to redirect to the edit page:
<div v-for="post in posts">
{{ post.title }}
<router-link :to="'/post/' + post.id + '/edit'">Edit</router-link>
</div>
Your routes would look like this:
[
{
path: '/posts',
component: require('./views/Posts.vue'),
},
{
path: '/post/:postId/edit',
component: require('./views/EditPost.vue'),
props: true,
},
]
The props configuration option is just to inform the Router to convert route params to component props. For more information see Passing props to route components.
Then in EditPost you'd accept the id and fetch the post from server.
export default {
props: ['postId'],
data() {
return {
post: null,
}
},
mounted() {
this.fetchPost();
},
methods: {
fetchPost() {
axios.get('/api/post/' + this.postId)
.then(response => this.post = response.data);
},
},
}
After the request has been completed, EditPost has its own copy which it can further process.
Note, that on every post edit and every time you enter the post list, you'll make a request to the server which in some cases may be unnecessary, because all needed information is already in the post list and doesn't change between requests. If you want to improve performance in such cases, I'd advise integrating Vuex into your app.
If you decide to do so, the components would look very similar, except instead of fetching the post to edit via an HTTP request, you'd retrieve it from the Vuex store. See Vuex documentation for more information.
if you don't want the params appear in the URL bar,you can use window.sessionStorage, window.localStorage or vuex.
Before you leave the view, set your parameters and get it after entering the new view.
You can use a prop on the <router-view :my-id="parentStoredId"></router-view> to pass down data present in the app.vue (main component). To change the parent data you need to emit a custom event comprising the value, from the childs (Posts.vue, EditPost.vue).
Another way is the Non Parent-Child Communication.
The way I prefer is Vuex. Even if it require you to learn the usage, it will repay back when the app grows.