vue.js router pagination
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.
vue-pagination.vue (link)
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.
view component setup (link)
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 ♥.