Vue Router: Keep query parameter and use same view for children

I'm rewriting an existing Angular 1 application with Vue.

The application always needs to authenticate an user by locale, id and token before entering any views. Respecting the conventions of our API, I specified the token as a query parameter within my main parent route.

Coming from the existing Angular's UI router implementation I thought this is the way to go:

// main.js
new Vue({
  el: '#app',
  router,
  store,
  template: '<router-view name="main"></router-view>'
})

// router.js
const router = new Router({
  mode: 'history',
  routes: [
    {
      name: 'start',
      path : '/:locale/:id', // /:locale/:id?token didn't work
      query: {
        token: null
      },
      beforeEnter (to, from, next) {
        // 1. Get data from API via locale, id and token
        // 2. Update store with user data
      },
      components: {
        main: startComponent
      },
      children: [{
        name: 'profile',
        path: 'profile',
        components: {
          main: profileComponent
        }
      }]
    }
  ]
})    

When I navigate to the profile view, I expect the view to change and the query token to stay, e.g. /en-US/123?token=abc to /en-US/123/profile?token=abc. Neither happens.

I'm using Vue 2.3.3 and Vue Router 2.3.1.

Questions:

  • Can I keep query parameters when navigating to child routes?
  • Am I using the Vue router right here? Or do I need to blame my UI router bias?

Solution 1:

You can resolve this in the global hooks of Router

import VueRouter from 'vue-router';
import routes from './routes';

const Router = new VueRouter({
  mode: 'history',
  routes
});

function hasQueryParams(route) {
  return !!Object.keys(route.query).length
}

Router.beforeEach((to, from, next) => {
   if(!hasQueryParams(to) && hasQueryParams(from)){
    next({name: to.name, query: from.query});
  } else {
    next()
  }
})

If the new route (to) does not have its own parameters, then they will be taken from the previous route (from)

Solution 2:

You can add in a mounted hook a router navigation guard beforeEach like this preserveQueryParams:

// helpers.js
import isEmpty from 'lodash/isEmpty';

const preserveQueryParams = (to, from, next) => {
  const usePreviousQueryParams = isEmpty(to.query) && !isEmpty(from.query);
  if (usePreviousQueryParams) {
    next({ ...to, query: from.query });
  } else {
    next();
  }
};
// StartComponent.vue
removeBeforeEachRouteGuard: Function;

mounted() {
  this.removeBeforeEachRouteGuard = this.$router.beforeEach(preserveQueryParams);
}

// don't forget to remove created guard
destroyed() {
  this.removeBeforeEachRouteGuard();
  // resetting query can be useful too
  this.$router.push({ query: {} });
}