One downside of browser rendered frontend frameworks is the temptation to hide structure or logic from your visitor. Your App might have a store, or component/route based data and produces on this hidden data a view. But on reload your visitor starts with your first page, or unfiltered list or something.

Let's go back to (old-)school and make all this "view producing data" for the user accessible and bookmarkable and shareable. As an example I show a pagination component, which will add a ?page=xx GET paramter to your route and your route will read that.

A demo project is available here: vue-pagination, a basic vue-cli project with a working example of this post.

For this example we keep the ui simple, just a forward/backward button and information about the current page and all available pages:

<template>
  <nav>
    <ul>
      <li>
        <router-link
          :to="paginateObject(currentPage - 1)"
        > « </router-link>
      <li>{{ currentPage }} / {{ totalPages }}</li>
      <li>
        <router-link
          :to="paginateObject(currentPage + 1)"
        > » </router-link>
      </li>
    </ul>
  </nav>
</template>

Instead of using an $emit('paginate',  pageTo) we use the <router-link> component with a function as :to binding. Let's look into components code:

export default {
  name: 'vue-pagination',
  data() {
    return {
      currentPage: null,
    };
  },
  props: {
    totalPages: {
      type: Number,
      required: true,
    },
    pageParameter: {
      type: String,
      default: 'page',
    },
  },
  methods: {
    paginateObject(pageTo) {
      return {
        query: {
          ...this.$route.query,
          [this.pageParameter]: pageTo,
        },
      };
    },
  },
  mounted() {
    this.currentPage = parseInt(this.$route.query[this.pageParameter], 10) || 1;
  },
  watch: {
    $route(to) {
      this.currentPage = parseInt(to.query[this.pageParameter], 10) || 1;
    },
  },
};

To make this component work, we need a totalPages property, to know how much we can paginate. The optional pageParamter property give you the power to use multi pagination components on one page, or to define your paging parameter. By default it is ?page=xx, if you like ?p=xx better, than set this property to this value.

paginateObject(pageTo)

 paginateObject(pageTo) {
   return {
     query: {
       ...this.$route.query,
       [this.pageParameter]: pageTo,
     },
   };
 },

This function is the key of this component. What it does, it returns an Object with a query property. Binding this kind of object :to a router-link component will stay on the same route, but add/remove/alter GET parameters of this route.

With ES-6 magic aka the spread operator, we clone the current query object from the route. Every other query parameter will be kept over paginating, for example a date-range or a active flag, when you are in a heavy data mask.

Next ES-6 magic aka computed property names adds the paging parameter we defined as property into our query object, in detail: [this.pageParameter]: pageTo will be computed to page: pageTo in runtime, or whatever property you put into pageParameter.

mounted() / watch $route

 mounted() {
   this.currentPage = parseInt(this.$route.query[this.pageParameter], 10) || 1;
 },
 watch: {
   $route(to) {
     this.currentPage = parseInt(to.query[this.pageParameter], 10) || 1;
   },
 },

The pagination component will determine itself on which page it is. So no currentPage property is needed. On mounted() and on $route changing it takes a look into the query object of the $route and will store the currentPage to it's data.

As a last step, your view component needs to watch to $route changes too:

import VuePagination from '@/components/VuePagination.vue';
export default {
  name: 'Home',
  components: {
    VuePagination,
  },
  watch: {
    $route(to) {
      this.$refs.console.value += `\nRoute Change detected: page: ${to.query.page} / tweetPages: ${to.query.tweetPages}`;
    },
  },
};

In a real world project, you will call a loadData function on $route changes and in your mounted() hook too.

Happy paginating!

Article Image from Hello I'm Nik via unsplash and ghost ♥.