👋 Hey there!

I'm Josh Morony, and this website is a collection of my free content centered around creating modern Angular applications using the latest Angular features, and concepts like reactive and declarative code.

Tutorial hero
Lesson icon

Reactive Pagination with RxJS and Angular

Originally published February 23, 2022 Time 5 mins

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:

  1. What I am cooking is what I am trying to display in the template, in this case, paginated data.
  2. 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.
  3. 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.