Vue 3 Composition API vue-router 4 breadcrumbs? - vue-router

I am trying to create a breadcrumb component.
I made this before with Vue 2 but couldn't manage to do again with Vue 3 as some things have changed and there aren't any guides on how.
I made a very simple route and added meta:
{
path: "/:serviceId?/app-showcase",
name: "AppShowcase",
component: () => import("#/pages/AppShowcase.vue"),
beforeEnter: authGuard,
meta: {
breadcrumb: "App Showcase", // For breadcrumbs in navbar
},
},
Want to have something like this:
Home / Services / fk39f / App Showcase
"fk39f" is a route param.
I was able to get the current route meta by:
<script>
...
const router = useRouter();
const route = router;
// Also getting some route params:
let serviceId = Number(route.currentRoute.value.params.serviceId);
</script>
<template>
<div class="bread-crumbs">
{{ router.currentRoute.value.meta.breadcrumb }}
</div>
...
</template>
Also user should be able to click to the breadcrumbs to go to that page. I tried some solutions like this but couldn't do it.
I couldn't find and solutions after searching for 2 hours.
If I can do this, I was also thinking of making a Vue 3 breadcrumbs library.
How can I achieve this?
Thanks!

Related

router-link-active class doesn’t work with different parameters values (Vue Router 4)

Hello everyone… I’m latin so my english is not very good looking.
I have this <router-link> component in my custom Navbar component.
<router-link :to="{ name: 'blog', params: { currentPage: 1 } }">Blog</router-link>
And this is my route definition:
{
path: "/blog/:currentPage",
name: "blog",
component: () => import(/* webpackChunkName: "blog" */ "#/views/Blog.vue"),
props(route){
const props = { ...route.params }
props.currentPage = parseInt(props.currentPage, 10)
return props
}
}
I have pagination component inside my Blog view.
<button
v-for="page in numberOfPages"
:key="page"
#click="$router.push({ name: 'blog', params: { currentPage: page } })"
:disabled="page == currentPage">
{{ page }}
</button>
I’m switching the page (ie the currentPage route parameter) from there but router-link-active class is only added to <router-link> with currentPage equal to 1 and none other, how can I keep active this <router-link> regardless the value of currentPage route parameter ? My pagination system must to be dynamic, I can't define a new child route for each page because don’t know how many pages will be.
This problem didn't exist in Vue Router 3 but I’m using Vue Router 4.0.3.
I tried to explaine my issue as well as I could.
Can anybody help me please…

VueRouter does not receive meta property in router-link tag

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.

VUE route redirect not closing modal from other page

I am using vue2 routes set up by another dev but I am trying to change the 'redirect' currently set to go to the 'home' page.
This issue is that there is a page which when you first enter the page (for the very first time) a bootstrap modal is displayed. The issue I'm having is if I enter this page of the first time then change the URL, the 'redirect' is working as expected BUT the modal is also still displaying and I don't know how to make it close.
routes.js
import {store} from './store/store';
import Cart from './components/Cart';
import Stock from './components/Stock';
import Home from './components/Home.vue';
import Review from './components/Review';
import Order from './components/Order.vue';
import OrderReport from './components/OrderReport';
import AccountDetails from './components/AccountDetails';
import ExistingOrders from './components/ExistingOrders';
export const routes = [
{path: '', component: Home},
{path: '/order', component: Order},
{
path: '/review', component: Review, children: [
{path: '', component: ExistingOrders},
{
path: ':id', component: OrderReport, beforeEnter: (to, from, next) => {
let orderCount = store.getters.orderPaginator.items.filter(item => item.id === +to.params.id).length;
next(orderCount > 0);
}
}
]
},
{path: '/account', component: AccountDetails},
{path: '/stock', component: Stock},
{path: '/cart', component: Cart},
{path: '*', redirect: '/order'}
];
If I change "redirect: '/order'" to "redirect: '/'" it goes to my home page as expected but with the modal displayed. Is there a way to close on the redirect?
Page where modal should be displayed
**Changed URL to 'orders' and clicked 'Enter'
Directed to the correct page (Home page) but modal still displayed. Is there a way I can change the :key using routes or would I have to use something like this.$forceUpdate(), although I have read that this may not work in vue2 and is really a nice thing to do.
What's my best approach? Just to mention I am new to vue2
I had success with this approach.
You can use a feature called Global Before Guards in Vue Router.
Inside router folder index.js file:
const router = createRouter({
// not relevant to the answer
history: createWebHistory(process.env.BASE_URL),
routes
})
router.beforeEach((to, from, next) => {
// remove modal backdrop if one exists
let modalBackground = document.querySelector('.modal-backdrop')
if (modalBackground) {
modalBackground.remove()
}
// do other stuff
next()
})
In my case I wanted to remove the modal backdrop but you can modify this to suit your needs.
Probably not the cleanest or best way to resolve the issue but the only way I could think of was to use the following in my 'home.vue' file
mounted() {
this.closeAccountSelectModal();
},
methods: {
closeAccountSelectModal() {
$('.modal ').modal('hide');
}
}
Used the class so all other pages which have modals will also be covered by it.
I know its too late but you can watch for route changes in components so when the route changes just close the modal
watch:{
$route (to, from){
// hide modal
}
}

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>

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.