- read

Infinite Scrolling in Angular: A hassle-free implementation

Dragos Becsan 73

Infinite Scrolling in Angular: A hassle-free implementation

Dragos Becsan
JavaScript in Plain English
8 min read2 days ago

--

Unveil the power of infinite scrolling in Angular with this step-by-step tutorial! No matter your skill level, I’ll guide you through a hassle-free implementation process that will elevate user engagement in your applications. Discover how to seamlessly load content as users scroll, creating a captivating and dynamic user experience.

In the first part we looked at how we can leverage RxJS to not only implement pagination via links but also make our code a lot more declarative — which helps us keep the code cleaner and more maintainable.

For those of you that haven’t read the first part, I highly recommend doing so as, although we are going to implement the infinite scroll pagination in this story from scratch, the other story highlights some important aspects on how to improve your code and also presents some of the key RxJS operators that we are going to use in this story as well. Link below:

As always, let’s start with the basics and define our goal.

Goal

From a business perspective, we are going to implement a Shop page in our Angular application where users can browse through all the products in our catalog. Our goal is to display an initial batch of products on the page, but as the user scrolls down we’ll going to add more and more products to the list until there are no more items left.

From a technical perspective, our goal is to implement the infinite scroll functionality using a single observable stream and keep the code clean, readable and maintainable.

Let’s dive into the code!

Project setup

We are going to use the latest versions of Angular and Bootstrap CSS, which, at the time of this writing, are:

  • Angular 16.2.4
  • Bootstrap 5.3.1

We are also going to use the following third-party library to get notified when we need to load more products as the user scrolls the page.

Open up a terminal window and run the following commands:

ng new angular-infinite-scroll --routing=false --style=scss
cd angular-infinite-scroll
npm install --save bootstrap ngx-infinite-scroll

We’ll implement our products listing in the AppComponent so we don’t need routing, but if you want to do it in a separate component, you might want to change the routing flag to true.

Now let’s import the Bootstrap SCSS in our styles.scss file.

Let’s also import the InfiniteScrollModule and the HttpClientModule in our AppModule.

And this should be it for the project setup. Let’s move on and take a look at how we are going to fetch our data.

Data retrieval

Since we need more data here than in the example with the pagination links, let’s use a public API to fetch our products this time.

Big shout-out to Ovi and the other contributors at dummyjson.com for putting together the project and hosting the API for us to use free of charge.

Let’s go back to our terminal and run:

ng g service api/products-api

Now let’s open the newly generated service and write out the code for fetching the products:

Before diving into a quick summary of what’s going on here, let’s also define the interfaces we’re working with. For the sake of simplicity let’s just create a new folder in app called models and, inside this folder, a new file called models.ts

Quick summary of the loadProducts$ method:

  • Using the HttpClient, we send a GET request to https://dummyjson.com/products to fetch our products.
  • We calculate the skip value based on the page number and the itemsPerPage that are requested. Given our formula, for page=1 and itemsPerPage=16 , the skip value will be 16 * (1 — 1) which returns 0 — so we’re requesting 16 products, starting from 0 (the first 16 products — 1 to 16). For page=2 , the skip value will be 16 * (2 — 1) which returns 16 — so we’re requesting 16 products starting from 16 (products 17 to 32) and so on for the other pages.
  • We send the number of items that we want (limit) and the calculated skip value as query parameters on the dummyjson.com request.
  • Once we get the response from the API, we modify it into a ProductsPaginator which is more useful for us given our pagination technique.
  • We use a simple formula to determine whether or not we have more pages. Keep in mind that this is not a generic formula. There are tons of ways to determine if there is more data to load and they mostly depend on what type of information regarding pagination the backend gives you on the response as well.

Now that we have a way to fetch our products, let’s use it in our component.

Infinite scroll implementation

As mentioned above, we are going to implement everything in the AppComponent so that we don’t overcomplicate things.

Let’s first look at the typescript file:

Just so we don’t get lost in the details, let’s already breakdown what’s going on here:

  • We define the page as a BehaviourSubject so that we can easily update it and respond to changes.
  • In the loadProducts$ method we setup our stream of data by reacting to changes of the page$ underlying value, fetching the products for the given page using the switchMap operator.
  • Using scan — which is actually the superstar of the infinite scroll technique — we add the products that we receive from new pages to the main list of products. In simple terms, what scan does and why it is useful here, is that it gives us access to the current unwrapped value of our observable via the accumulator parameter and the value from the current emission via the value parameter so that we can manipulate the data however we want and send it forward. Think of it like we only have one single paginator at all times and it’s the one that we generated for the first page — hence the if (value.page === 1) { return value; } . When the paginator for page 2 comes in from the API, we only use it to add the products that it brought to the items list of the previous paginator and also set the values of the page and hasMorePages, as we need those to depend on the latest page. The same thing happens for all the other pages. We take the items, add them to the items list of the main paginator, update the page and hasMorePages values and continue with the main paginator.
  • We assign this stream of data to the paginator$ property so that we can subscribe to it in the template via the async pipe.
  • And finally, we have the loadMoreProducts method that receives the value of the paginator and checks if there are more pages to load. If so, we increment the page value by 1, triggering the pipeline that we defined in loadProducts$ (and assigned to the paginator$ property) and so adding new products to our list. The method is called in the template when the user has almost reached the bottom of the page via scrolling.

Now that we got a better understanding of how the data flows in our component, let’s have a look at the template:

  • We subscribe to the data stream that we defined in the paginator$ observable, using the async pipe.
  • We use the infiniteScroll directive provided by the ngx-infinite-scroll package so that we get notified when the user has almost scrolled to the end of the page. We set the value of the infiniteScrollDistance to 2 — which means that we’re going to receive a scrolled event when there is only 20% left of the page to scroll — and the value of infiniteScrollThrottle to 50 — which tells the directive to wait 50ms after the user has stopped scrolling before sending the scrolled event. You can read more about these properties in the docs.
  • When we receive a scrolled event, we call the loadMoreProducts method, passing the latest paginator emission as an argument. As we already know from the breakdown of the typescript file above, this method will trigger the paginator$ pipeline which will run with the value of the new page and so bring in new products and updating the page and the hasMorePages values.
  • If there are no more pages to load, we display a message to the user to let them know that there are no more products to load.

And that’s it! 🎉 We’ve just implemented the infinite scrolling technique in a declarative way using clean and readable code.

Let’s also add a bit of CSS so that our product thumbnails are resized to a fixed height while maintaining aspect ratio. Either put those rules in the app.component.scss file or in styles.scss as I did:

Now spin up your development server via ng serve and let’s have a look at the end result:

Requests are being sent as the user is about to reach the end of page

Wrap-up

Looking at our goal, I would say we have successfully achieved it. Users can now scroll through our whole catalog without the need to perform any other interaction and we have implemented everything using clean, reactive code.

Full source code can be found on GitHub:

Thank you very much for following along until the end! 🎉

In the next story we will continue to build on this example and look at how we can introduce the concept of filtering products using the same single stream of data.

If you found this tutorial useful please hit that clap button and follow me to get notified when I publish future stories.

I was also thinking about doing this kind of tutorials in video format as I would be able to provide more explanations as I write up the code. What do you guys think? Let me know in the comments.

Have a nice day, guys! I’ll see you in the next one!

Resources

https://www.learnrxjs.io/learn-rxjs/operators/transformation/scan
https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap
https://angular.io/api/common/AsyncPipe
https://www.learnrxjs.io/learn-rxjs/subjects/behaviorsubject

In Plain English

Thank you for being a part of our community! Before you go: