Adding Pagination with Infinite Scroll
At the moment, we just get a bunch of GIFs, display them, and that’s it. We are going to improve this a little now. Instead of just dumping everything we get from a single request, we are going to only display a set amount of GIFs per “page”. When the user scrolls to the bottom of the page, we will automatically load in more GIFs if they are available.
To do this, we will need to make some changes to our gifsLoaded$
stream, and
we are also going to make use of the third party ngx-infinite-scroll
library
to help us trigger a load when the user scrolls to the bottom of the page.
Adding Support for Pagination
Before we can trigger a new page with our infinite scroll mechanism, we need a way to actually trigger and load new pages.
We will do this by extending our RedditService
with a pagination$
source. We
are going to have to make some other changes as well.
When we are loading data from the Reddit API we do not just load specific pages of data, e.g:
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&page=1
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&page=2
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&page=3
We will instead request data after the name a specific post, like this:
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&after=somepost
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&after=someotherpost
https://www.reddit.com/r/${subreddit}/hot/.json?limit=100&after=anotherpost
We will need to keep track of the name of whatever the last known gif we
have seen is — as in the gif at the very end of our list. We will then use the
name of that gif as the after
when we are trying to load a new page.
This means we are going to need to make some changes to our state. In fact, there are other things we will eventually want to store in the state too, so let’s handle all of that now.
Update the
GifsState
to reflect the following:
export interface GifsState {
gifs: Gif[];
error: string | null;
loading: boolean;
lastKnownGif: string | null;
}
Update the initial state to the following:
private state = signal<GifsState>({
gifs: [],
error: null,
loading: true,
lastKnownGif: null,
});
Let’s also add some more selectors to select our new state.
Update the selectors to match the following:
// selectors
gifs = computed(() => this.state().gifs);
error = computed(() => this.state().error);
loading = computed(() => this.state().loading);
lastKnownGif = computed(() => this.state().lastKnownGif);
The next thing we will need is a new source
that we can next
when we want to
trigger a new page loading — we will call this pagination$
. We will also need
to update our gifsLoaded$
source to react to the pagination$
source
emitting, and whenever it does it should trigger a new request to fetch data
from Reddit.
For now, we will just repeat the same request rather than worrying about
tracking the lastKnownGif
. Just to get the basic set up working.
This is by no means an easy task, but see if you can figure out how to add the
pagination$
source and cause the gifsLoaded$
to emit with the data from an
HTTP request to the Reddit API every time that we call next
on pagination$
.
Click here to reveal solution
Solution
Update the sources to reflect the following:
//sources
pagination$ = new Subject<void>();
private gifsLoaded$ = this.pagination$.pipe(
concatMap(() => this.fetchFromReddit('gifs'))
);
Now rather than gifsLoaded$
immediately making a single request to Reddit, it
will make one every time that pagination$
emits. If we used switchMap
here
it would cause the currently executing request to be cancelled and we would
switch to the new request every time that pagination$
emits. This is why we
use concatMap
instead. If the user were to trigger two page loads in quick
succession, we wouldn’t want the first request to be cancelled. This way, the
concatMap
would wait for the first request to finish, and then it would start
the second request.
The problem here though is that now no gifs will load until we manually trigger the pagination source. That’s a bit annoying and doesn’t feel very reactive/declarative.