In most web applications you will encounter pages with data rendered in the form of list. Since sets of data are usually huge, you either make use of infinite scroll technique and append items as you scroll down the page or handle the issue with the aid of pagination 📖. If you take the latter approach, it may be good enough to reuse some existing implementations like the ones by ng-bootstrap or Angular Material. However, sometimes picking a low hanging fruit 🍏 is not enough to make a UX designer wish come true 😃 and you need to come up with a custom solution. I’ve recently been in such situation which inspired my to write this blog post. I’ll present how to implement the Angular directive which can be applied to UI widgets of your choice and get the pagination functionality out of the box 🚀.
The custom paginator needs to have an input field which enables entering the desired page number 6️⃣ 9️⃣. In addition, it should only accept natural numbers and any attempt to provide invalid input must be ignored 🚫. The directive’s change event should be fired once the input’s native change event has fired (on blur or enter key press if a value has changed). If a user types an out of range number, the last page 🏁 should be selected once the change is committed (blur or enter key press events). Last but not least, if a user leaves the input field empty, the first page 1️⃣ needs to be selected.
It’s also desirable to expose methods for navigating straight to first, last, previous ◀️ and next ▶️ pages.
When it comes to the Angular directive, it has two input properties, namely pageNo and totalPages. The first one enables providing the current page in a declarative way and is especially useful when the directive mounts. The second one is self-explanatory. Of course, the directive has to take security measure in order to be bullet-proof 🔫 when the input data is invalid 😨.
The idea is to provide an easy way to reuse the pagination logic which in Angular application means encapsulating the logic in a directive or creating a component which accepts Template Refs with custom UI widgets. If you’re not familiar with Template Refs, I highly encourage you to take a look at one of my previous posts ▶️ here.I decided to create a directive, since the number of UI elements that can be customized is not small (up to 5 items) and you may want a different behavior when the paginator is in certain state. For example, when the first 1️⃣ or last 🏁 page is selected, you may wish to do something with ‘go to first’, ‘go to previous page’ or ‘go to last’, ‘go to next page’ buttons 🔘. You can either disable the elements, hide them or apply a given CSS class. Just think of the number of input properties responsible for configuration if the paginator was implemented as component 🔥 😱.
Let’s take a look at parts of the directive’s code before I’ll present the whole implementation 😏
The directive injects two services in order to interact with the underlying DOM element. In addition, it is exported, so that you can access an instance in a template.
It has the setValue method which is responsible for setting the underlying input element’s value and the setPage method which performs actions when the selected page has changed.
The directive exposes public methods for direct imperative navigation to a desired page ✈️.
In addition, it has two public getters which indicates certain paginator’s states.
When the input event fires, the directive takes care of removing invalid characters 🚫.
When the change event fires, the input element’s current value is checked and altered if needed (empty or out of range value). Next the directive’s pageChange event is fired.
Last but not least, input properties changes are handled taking into account invalid values.
The directives’ entire code is as follows:
Let’s see how the directive can be used:
You just need to apply the directive to an input element and create a template reference variable (#pagination) which points to the directive’s instance. You can interact with the paginator via the #pagination variable. Instead of using native button elements, you can use any template you wish and simply call the paginator’s methods when appropriate. In addition, you can do whatever you want with the information that the first or last page is selected (pagination.isFirst / pagination.isLast). Last but not least, it’s totally up to you which controls are present.
In this blog post I presented how to encapsulate pagination logic in Angular directive and use it with a template of your choice. The presented approach is an alternative to wrapping the logic in a component with a great number of input config properties, Template Refs and CSS overriding.
Feel free to play around with the example:
I hope you liked the post and learned something new 👍 If so, please give me some applause 👏
Big thanks to Bartosz Cytrowski for a code review! 😃