I’ve been doing a few videos on reactive coding with RxJS and Angular recently.
The key idea is generally that we have an observable stream, and then we use the
| async
pipe in the template to pull data out of that stream to display it.
This means we never have to manually subscribe and handle the data imperatively because the async pipe handles the subscription for us. But pagination creates a bit of a trickier situation, and in this blog post, we’ll explore how to handle it reactively.
This article is adapted from the following video:
BehaviorSubject for the Current Page
When coding reactively, I like to consider what it is exactly that I am “reacting” to. In this case, I want to react to the page changing. Since that is what I am reacting to, I want that to be a stream.
export class HomePage {
currentPage$ = new BehaviorSubject(1);
constructor(private passengerService: PassengerService) {}
nextPage() {
this.currentPage$.next(this.currentPage$.value + 1);
}
prevPage() {
if (this.currentPage$.value > 1) {
this.currentPage$.next(this.currentPage$.value - 1);
}
}
}
That is why we have set up currentPage$
as a BehaviorSubject
.
A BehaviorSubject
is an observable stream with additional properties, two of
which are useful to us in this scenario: we can get its current value without
having to subscribe, and we can cause it to emit new values on the stream by
calling the next
method.
We set up this stream with a default value of 1
, and then when the user clicks the next or previous page buttons, we just get the current value and add one or take one.
Combining Streams for Pagination
Now we need to mix some streams together. I like to think of this like a recipe:
- What I am cooking is what I am trying to display in the template, in this case, paginated data.
- The ingredients for this dish are the streams I need to make that happen. In this case, I am going to need two streams: a stream of the page number so that I know what page data to use, and a stream of the actual data from the API.
- The method for this dish is how these streams are combined together using RxJS operators.
export class HomePage {
currentPage$ = new BehaviorSubject<number>(1);
currentPageData$ = this.currentPage$.pipe(
switchMap((currentPage) =>
this.passengerService.getPassengerData(currentPage)
),
map((res: any) => res.data)
);
constructor(private passengerService: PassengerService) {}
nextPage() {
this.currentPage$.next(this.currentPage$.value + 1);
}
prevPage() {
if (this.currentPage$.value > 1) {
this.currentPage$.next(this.currentPage$.value - 1);
}
}
}
You can see how I am combining those streams together here. currentPageData
is
the final stream that I am subscribing to in the template with the async pipe:
<ng-container *ngIf="currentPageData$ | async as passengers; else loading">
<ion-list>
<ion-item *ngFor="let passenger of passengers">
<ion-label>{{passenger.name}}</ion-label>
</ion-item>
</ion-list>
</ng-container>
We make this stream by first taking the currentPage
from our first stream, and
then we use the switchMap
operator to return a new stream. The switchMap
operator will basically take a data emission from a stream, and then go
subscribe to a different observable stream instead.
We pass currentPage
into our getPassengerData
method which is going to
return this observable stream:
getPassengerData(page: number) {
return this.http.get(
`https://api.instantwebtools.net/v1/passenger?page=${page}&size=10`
);
}
We are using the HttpClient
to get the data from an example API using the page
number from our currentPage
stream.
That is the key part, but we also continue to modify this stream by piping on
another operator. The next map
operator just takes the data that is emitted
and maps it to return just the data array within that. This is just where this
particular API we are using stores the data we are interested in. I don’t want
the whole JSON response, just this one array.
We then just supply this stream to the async pipe in our template, and now this
is going to display the data for the appropriate page whenever the user emits
a new value on the currentPage
stream by clicking one of these buttons.
Final Result
Make sure to check out the video for a look at the final result. I’ve intentionally left things like loading states or animations for the data off for this tutorial to keep things focused on just the actual pagination. However, handling loading states is also something very possible with this reactive approach.