0

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?

Raeisi
  • 1,647
  • 1
  • 6
  • 18
Antonio Dragos
  • 1,973
  • 2
  • 29
  • 52

1 Answers1

1

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.

Raeisi
  • 1,647
  • 1
  • 6
  • 18
  • Thanks for your reply. Are you sure it's legal to define 2 routes with the same name? – Antonio Dragos Aug 04 '22 at 09:36
  • Sure. Vue Router supports `alias` that can be a path or an array of paths. https://github.com/vuejs/vue-router/blob/dev/examples/route-alias/app.js React Router event support and an array of paths for a specific component. It's a matter of reusability. – Raeisi Aug 04 '22 at 11:46
  • 1
    It's legal to have the same `name` multiple times, but when you navigate to `{ name: duplicatedName }` it will always resolve to the first match in the routes tree. I'd avoid it, on general principles. – tao Aug 04 '22 at 12:33