The article is originally published to my blog: Angular and RxJS Infinite Scroll
Infinite Scroll is the ability to scroll through a large infinite list without compromising the performance of application. It improves user experience of the application. In this article I will cover how to implement infinite scroll with API in Angular 12 with the help of RxJS Operators.
How does Infinite Scroll Works
A question comes in mind that how it works internally. It works like there is a big list say 10k items are there. Displaying all those items in once is not possible as it will break the application. And you can not put numbering pagination as that will look bad on the place or you have dropdown (dropdowns can not number paginations).
In that case you need infinite scroll. It loads only 10 (size is provided in configuration) records at a time and every time user scrolls to the end of list, application automatically calls the API to fetch next 10 records. So as soon as you reach end of the list, it fetches you the next 10 records. It gives you next results till all the results are finished in database.
Note: I am using Angular 12 and latest versions of RxJS Operators for this example. However the code should work on below versions too. But if you face any issue implementing it in below version, let me know in comments I will check them.
Let's implement infinite scroll using RxJS
Setup an Angular application
You can skip this step if you have an existing angular application.
Create a application with below command:
ng n angular-rxjs-infite-scroll
Create a Service file
You can skip this step if you have already a service file.
Run the command to create a service file: app.service.ts
ng g s app
Import HttpClientModule in app.module.ts
Import the HttpClientModule
package the application module. It is required to consume the Http APIs.
import { HttpClientModule } from '@angular/common/http';
// add in imports
imports: [
// other modules,
HttpClientModule
]
Consume API
The next step is to create a method getData
in app.service.ts
file to fetch the API results. I am using JSON PlaceHolder Fake API.
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AppService {
constructor(private httpClient: HttpClient) { }
public getData(pageNumber: number, pageSize: number) {
return this.httpClient.get(`https://jsonplaceholder.typicode.com/photos?_start=${pageNumber}&_limit=${pageSize}`);
}
}
Create Infinite Scroll Logic
Create logics to scroll items infinitely. For this purpose I have used following RxJS Observables / Operators:
- BehaviorSubject– to store the fetched items
- forkJoin– to call multiple observables simultaneously
- fromEvent– to generate scroll event on div
- map– to get only scrollTop data
- take– to take the last items from existing fetched data
Here is the complete code.
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, forkJoin, fromEvent, Observable } from "rxjs";
import { map, take } from "rxjs/operators";
import { AppService } from './app.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
obsArray: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
items$: Observable<any> = this.obsArray.asObservable();
currentPage: number = 0;
pageSize: number = 10;
constructor(private appService: AppService) { }
ngOnInit() {
this.getData();
}
private getData() {
this.appService.getData(this.currentPage, this.pageSize).subscribe((data: any) => {
this.obsArray.next(data);
});
const content = document.querySelector('.items');
const scroll$ = fromEvent(content!, 'scroll').pipe(map(() => { return content!.scrollTop; }));
scroll$.subscribe((scrollPos) => {
let limit = content!.scrollHeight - content!.clientHeight;
if (scrollPos === limit) {
this.currentPage += this.pageSize;
forkJoin([this.items$.pipe(take(1)), this.appService.getData(this.currentPage, this.pageSize)]).subscribe((data: Array<Array<any>>) => {
const newArr = [...data[0], ...data[1]];
this.obsArray.next(newArr);
});
}
});
}
}
Code Explanation:
this.appService.getData(this.currentPage, this.pageSize).subscribe((data: any) => {
this.obsArray.next(data);
});
Above code will get you the first 10 items to bind data on page load.
const content = document.querySelector('.items');
const scroll$ = fromEvent(content!, 'scroll').pipe(map(() => { return content!.scrollTop; }));
Above code block catches the scroll event on div.
let limit = content!.scrollHeight - content!.clientHeight;
if (scrollPos === limit) {
// other codes here
}
Above code block checks whether you have scrolled through the end of items. If yes, do whatever code you want to do in the if
block.
this.currentPage += this.pageSize;
forkJoin([this.items$.pipe(take(1)), this.appService.getData(this.currentPage, this.pageSize)]).subscribe((data: Array<Array<any>>) => {
const newArr = [...data[0], ...data[1]];
this.obsArray.next(newArr);
});
This code block will increase the current page records and fetch the next page data. Once the data is received, merged the received data with existing array and provide to BehaviorSubject
observable. That will refresh the bind data on page and show you the new data (while keeping the existing data in list).
Do Some Styling
Add some css to restrict height of the div and display scroll bar.
app.component.scss
.items {
max-height: 300px;
width: 460px;
overflow: scroll;
}
Template Code
Add following html to your app.component.html
file.
<div class="items">
<div *ngFor="let item of items$ | async">
<b>ID: </b> {{item.id}}<br>
<b>Title: </b>{{item.title}}<br>
<img src="{{item.thumbnailUrl}}" alt="">
<br>
<br>
<br>
</div>
</div>
Output
Its time to run the app and check output. Check the browser. You should see the infinite scroll in your div.
Conclusion
The article is originally published to my blog: Angular and RxJS Infinite Scroll
In this article we covered how to implement a server side infinite scroll in Angular using RxJS Operators.
Must Read Articles on Angular:
Top comments (0)