DEV Community

Cover image for Vue 2 Spa Routing, Back buttons
Mykolas Mankevicius
Mykolas Mankevicius

Posted on

Vue 2 Spa Routing, Back buttons

Disclaimer: 
Using `Vue 2` with `@vue/composition-api`
Enter fullscreen mode Exit fullscreen mode

Ever wandered how to make a custom back button?

It might seem trivial, right?

Just `router.go(-1);`
Enter fullscreen mode Exit fullscreen mode

But what if you want to act it more like a breadcrumb, where the back button goes from the details view back to the listing page?

Seems simple enough:

router.push({name:'SomeList'})
Enter fullscreen mode Exit fullscreen mode

Ok but what if that list has query/search/pagination?

Well for that you will need to start tracking some information in one place or other.

There is a plugin for vue:
Vuex Router Sync

But if you're not using Vuex or you try to avoid depencies where possible, there's a simple solution that i've come accross.

The solution:

Sync your query to the current route

first we need to sync query params to vue router:

import { removeNullProperties } from './useUtilities.js';
import { useRouter } from './useRouter.js';

// composable function
export const syncQuery = (query) => {
  // injected vue router instance
  const router = useRouter();

  const updateQueryParams = () => {
    const url = new URL(window.location);
    // reseting the search as window.location includes the current query params as well
    url.search = '';

    // removing any null/empty properties so that the url stays a clean as possible
    Object.keys(removeNullProperties(query)).forEach((key) => {
      url.searchParams.set(key, query[key]);
    });

    const { pathname, search } = url;
    const newPath = `${pathname}${search}`;
    const currentPath = router.currentRoute.fullPath;

    // if the path changed let's update vue router
    if (newPath !== currentPath) {
      router.push(newPath);
    }
  };

  // watched to watch query for changes
  watch(
    () => query,
    () => {
      updateQueryParams();
    },
    { deep: true }
  );
};
Enter fullscreen mode Exit fullscreen mode

Use vue router global resolve guard

Now we need to store some meta information, you have two simple options when using vue router Global resolve guards beforeResolve or beforeEach

in this example i'll use beforeResolve

router.beforeResolve = (to, from, next) => {
  if (!to.meta) {
    to.meta = {};
  }

  to.meta.from = {
    name: from.name,
    params: from.params,
    query: from.query,
  };
}
Enter fullscreen mode Exit fullscreen mode

This adds all we need to navigate back to the from route.

A composable to get you a true path back

Next i've wrote another composable function to use on the custom back button:

import { useRouter } from './useRouter.js';

export const useRouteBack = (route) => {
  const router = useRouter();

  const from = router.currentRoute.meta?.from;
  if (from && from.name === route.name) {
    return from;
  }
  return route;
};
Enter fullscreen mode Exit fullscreen mode

which you can use simply like so:

<router-link :to="useRouteBack({name: 'SomeList'})>
back to list
</router-link>
Enter fullscreen mode Exit fullscreen mode

That's it. I've spent quite some time gathering all the bits and bobs, but this solution seemed like the one involving the least amount of effort/code.

Initializing list page from the query

This will really depend on your solution, but this is the most important part to make things work.

In essence you need to make sure your list page can initialize while using the router.query params.

What i've done is add router query handling to the List pages so that my first calls to the api match the initial params provided from the url.

This is a very custom solution for each page.
Since you could have a page with complex filters which v-model your query with Objects and your url/api only needs the simple data structures like

id: number|string
date: string
name: string
Enter fullscreen mode Exit fullscreen mode

I'm still in the process of updating all the list pages.

Maybe if i find some common ground which i could refine into a nice function/pattern i'll update this post.

Hopes this helps, and Good Luck with your coding adventures!

Top comments (0)