VueJS: one component for many routes - vue.js

I'm a newbie to the world of VueJS.
My app uses vue-router to handle with routes changing. This's how it's configured:
new Router({
routes: [{
path: "/",
component: Articles
}, {
path: "/articles/:category",
component: Articles
}]
});
As you can see, I've got 2 routes handled by the same component. The idea is very simple. When we're on a home page, show articles from any category. When we're on a category page, show articles from that category.
The question is, how a component can detect whether the route's changed, e.g, from home page to category page?
I've come with this solution:
function loadArticles() {};
Vue.component("Articles", {
mounted: loadArticles,
watch: { "$route": loadArticles }
});
But I think, that's a bad solution.

Here's what I have done on a little project of mine, I'm using the template to display single article
This is my router
'/post/:id': {
name: 'post',
component: require('./components/article.vue')
},
In my .vue file, I get the $route.params to get the id of the article then displayed it in my ready function.
$.get('api/posts/' + this.$route.params.id, function(data){
myPost.current = data;
})
You'll need to get the category parameter then rendered the template according to it.

It should look something like this:
watch: {
'$route' (to, from) {
if (this.$route.params.category) {
this.loadArticles(category);
} else {
this.loadArticles();
}
}
}
'watch' is recommended here, in docs.

Related

Mapping multiple URLs to the same component with Vue Router

In my Vue 2.7.5 app (using Vue Router 3.5.4). I'm trying to map multiple URLs to the same component. Currently, I have a single route mapped to the component:
{
path: '/customer/:customerId',
component: CustomerOrders
}
My goal is to add an optional orderId parameter, such that if a URL like /customer/42/order/59 is accessed, then the same component is loaded, but the order with ID 59 is highlighted (the details of how the param is going to highlight the order are not important).
I tried changing the path to /customer/:customerId/orders/:orderId?, but this would no longer match any URLs of the form /customer/:customerId and would therefore be a breaking change.
My current solution is to use a child route:
{
path: '/customer/:customerId',
component: CustomerOrders,
children: [
{
path: 'order/:orderId',
component: CustomerOrders
}
]
}
This work as the CustomerOrders component is loaded by paths matching either /customer/:customerId or /customer/:customerId/order/:orderId, but it seems like a slightly convoluted approach and I'm not sure it's an appropriate use of child routes.
Is there a better solution?
The easiest way is to register the same component for both routes:
{
path: '/customer/:customerId',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
},
{
path: '/customer/:customerId/order/:orderId',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
},
An exact solution that you are looking for is parsing params manually:
{
path: '/customer/:param+',
name: 'CustomerOrders',
component: () => import( '../views/CustomerOrders.vue'),
props: router => {
const params = router.params;
const split = params.param.split('/');
params.customerId = split[0];
if (split.length > 2) {
params.orderId = split[2];
}
},
},
Here the :params+ ensures that a customerId and the rest of the route get caught. On the other hand, using :params* catches the /customer route without even a customerId.
CAUTION This approach also /customers/42/...everything...
The vue3 solution is solved here.
EDIT: the following approach cannot catch orderId
Using an alias improves reusability and reduces rendering time but comes with a price of capturing params-change in a watch handler.
{
path: '/customer/:customerId',
name: 'CustomerOrders',
alias: '/customer/:customerId/order/:orderId',
component: () => import( '../views/CustomerOrders.vue'),
},
In this case, your component doesn't get rebuilt by changing routes and also onMount or beforeCreate hooks don't get called either. To catch params-change add a proper watch:
export default {
name: 'CustomerOrders',
watch: {
'$route.params'() {
console.log('params changed. Extract params manually and reload');
},
},
};
This issue is addressed here.

How update a VUE component changing URL param in current page

In my vue SPA I have a simple router like this:
{path: '/page/:userId', name: 'user', component: user},
Which means an url like http://xxx.xxx/user/1 shows the component user having the content about user 1.
In case on the same page I have some link as
<vue-link :to="{name:'user', params={userId:3}}">
The router update only the URL but the content page (because it assumes the page is the same where I'm at)
My user content loads data using the url params in data and in watch too
data
userId: this.$route.params.userId || 1,
watch
watch: {
userId: function () {
return this.$route.params.userId || 1;
},
How to fix it without using router-view?
Thanks for any suggestion
If I correctly get your problem which is that you are not able to track your route change. You need to watch the route change, whenever your route changed on a same component it should do something.
watch: {
$route() {
this.updatePage(this.$route.params.userId)
}
},
methods: {
updatePage(param) {
// call api / do something
}
}

Set default meta properties on Vue Router

I have searched the documentation here, and whilst there is a meta property on the VueRouter object, it doesn't seem to be doing anything (there is no description on the actual property in the docuementation)...
Consider the following routes:
let routes = [
{
path: '/',
component: require('./views/Home').default
},
{
path: '/about',
component: require('./views/About').default,
meta: {
transitionColor: '#000' // Note this property
}
}
];
I would like to do something like this:
export default new VueRouter({
mode: 'history',
routes,
meta: {
transitionColor: '#fff'
}
});
The intention of the above code is to set the default $route.meta.transitionColor to #fff for all routes, and then allow the route to override this if it is supplied in the route level meta.
Is there a way to set default meta properties on Vue Router in this way?
To my knowledge, it does not exist. However, with navigation guards, you can achieve something very similar.
router.beforeEach((to, from, next) => {
// manipulate route (e.g. set meta)
});
NavGuards are pretty handy - here's an simple example how to achieve something like you requested but using another meta-property - here called in router/index.ts somewhere after createRouter was invoked. The example declares every page without an explicitly auth-declaration as private using the custom meta-property requiresAuth (and sets the page title afterwards, while we're on it):
router.beforeEach((to, from) => {
if (!Object.prototype.hasOwnProperty.call(to?.meta, "requiresAuth")) {
to.meta.requiresAuth = true;
}
document.title =
"APP BRAND - " + i18n.global.t(`component.appNav.${String(to.name)}`);
next();
});

Fetching before navigation with multiple components using vue-router

Trying to figure out what is the best way to fetch some data before navigating to some routes that I have.
My routes are:
let routes = [
{
path: '/',
component: require('./views/Home.vue')
},
{
path: '/messages',
component: require('./views/Messages.vue'),
},
{
path: '/posts',
component: require('./views/Post.vue'),
},
{
path: '/login',
component: require('./views/Login.vue')
},
{
path: '/dashboard',
component: require('./views/Dashboard.vue'),
}
];
For /messages, /posts and /dashboard I want to fetch some data and while doing that show a spinner.
Vue-router docs suggest to use beforeRouteEnter, Fetching Before Navigation
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
}
But my question is, must I implement this fetch logic in all my three view components?
Since it's the same data being fetched I don't want to repeat the logic in every component.
The beforeRouteEnter isn't called in my root component where I have this <router-view></router-view> otherwise I feel that it would be the best place to have this logic.
I'm really new to vue and this one is a really hard nut to crack for me.
Are you aware of vuex? Since you need to share the same data on three different pages, which sounds like a job for vuex store. Basically you can import the store into your router, and set data in the store instead of on the component, and then you can conditionally fetch the data by checking the status of the data in the store and if the data already exists, don't fetch. Something like the following:
import store from './store'
beforeRouteEnter (to, from, next) {
if (store.state.post === null) {
getPost(to.params.id, (err, post) => {
store.dispatch('setPost', post)
next()
})
}
}
Assume your store has a state called post and action called setPost.
const store = new Vuex.Store({
state: { post: null },
actions: {
setPost ({ commit }, post) {
// set the post state here
}
}
})

Unable to understand how vue-router query params work

I am building an app with the following route settings.
export default new Router({
routes: [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/home',
name: 'Dashboard',
component: Dashboard,
beforeEnter(to, from, next) {
if (!store.getters.isLoggedIn) {
next('/login');
} else {
next();
}
}
},
{
path: '/',
redirect: '/home'
}
]
});
I also set query parameters based on some select boxes I have by using $router.push say as follows.
this.$router.replace({
query: {
scope: this.$store.getters.filterScope
}
});
Problems I have
Reloading the page removes the query params from the URL.
Updating one query parameter removes the other one I had. Example - updating scope using the above method removes the dateRange parameter I already had.
I am using Vuex too so is there any way I can manage these query params using the store?
If you store your parameters in vuex and you don't use vuex-persistedstate, when you refresh the page state will be removed.
So, my suggestion for you:
your-awesome-site.com/dashboard?first=hello&second=hi
In your Dashboard component, you can do like this
mounted () {
console.log(this.$route.params)
// Update vuex state filterScope
}
So you will retrieve your parameters from url and save it in vuex.
Also you can solve your second issue using this.$route.params