Intersection Observer API: Lazy Load Scroll Data on Angular.

Afif Alfiano
6 min readMay 27, 2024

--

Intersection Observer API

Have you ever thought about what looks like the mechanism of libraries for an infinite scroll? They will load or request the data once the scroll indicator until to the bottom of the bar. How can it be?

So, today I’m gonna share a bit of tutorial about how to create a simple infinite scroll using intersection observer. So, we can create it with the Intersection Observer API, and no need to install a third library. It can reduce the bundle size of the project.

As a developer, we should have a mindset to not always install third-party libraries to cover up the feature if the feature can built from scratch. it’s a good mindset for developers and will improve your skills.

Intro

What is an Intersection Observer? Based on the developer.mozilla.org the definition of that thing is:

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.

Simply, we can use an intersection observer to support lazy load when loading data on scrolling.

Setup

I would like to use angular for this tutorial and you can initialize the project or maybe clone it from this repository. https://github.com/afifalfiano/angular-tutorial-blogs

Run Project

After that, you can run the project to ensure there is no error on the project.

First init run the project

Great, the project is running well. So, we can jump into the next step.

API

Here, we’re gonna use the API from https://randomuser.me/documentation#pagination and use the pagination as well.

Consume API

The next step is to consume API as usual on Angular. We need to import HTTPClientModule to use httpClient.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';


@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Next, update app.component.ts to consume API and render the data.

import { Component, OnInit } from '@angular/core';
import { RandomUserService } from './services/random-user.service';
import { Payload } from './interfaces/requests/payload';
import { HttpErrorResponse } from '@angular/common/http';
import { User } from './interfaces/responses/user';
import { Info } from './interfaces/responses/results';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'angular-tutorial-blogs';
userList: User[] = [];
info?: Info;
constructor(
private randomUserService: RandomUserService
) {}

ngOnInit(): void {
this.getRandomUser();
}

getRandomUser(): void {
const payload: Payload = {
results: 10,
page: 1
};
this.randomUserService.getRandomUser(payload).subscribe({
next: ({info, results}) => {
this.info = info;
this.userList = results;
console.log(this.userList)
console.log(this.info)
},
error: (err: HttpErrorResponse) => {
throw new Error(err.message);
}
})
}
}

Update app.component.html to render variables from response hit API.

<h1>Random User List</h1>
<div class="wrap-user">
<p>Current Meta: </p>
<span>Total: {{info?.results}}</span> |
<span>Page: {{info?.page}}</span>
<ul style="overflow-y: scroll; max-height: 100px;">
<li *ngFor="let user of userList;">
{{user.email}}
</li>
</ul>
</div>

So, the user interface will be like this. Just a simple UI.

How to consume API on Angular

Finish, we have already successfully hit API and rendered the response on the HTML.

I want to add a feature for infinite scroll to hit API. Once the user scrolls until the bottom of the maximum height element (bar), it will automatically hit API for the next pages with the same limit. It is time to use an intersection observer.

Infinite Scroll

I will give you a simple demo of this feature.

Intersection Observer

Interesting, isn’t it?

Okee, Let’s jump to the next step.

First, update the app.component.html

<h1>Random User List</h1>
<div class="wrap-user">
<p>Current Meta: </p>
<span>Limit: {{info?.results}}</span> |
<span>Page: {{info?.page}}</span> |
<span>Total: {{userList.length}}</span>
<ul style="overflow-y: scroll; max-height: 100px; height: 100px;">
<ng-container *ngFor="let user of userList; let i = index; let first = first; let last = last">
<li *ngIf="!last">
{{user.email}}
</li>
<li *ngIf="last" #targetObserver [id]="'last-list'">
{{user.email}}
</li>
</ng-container>
</ul>
</div>

We need to separate the last element that needs to be attached intersection observer and a normal element. So, I use the last feature from ngFor itself, and on the element list I put template reference targetObserver with the id name is last-list.

Next, update the app.component.ts

  @ViewChild('targetObserver') targetObserver?: ElementRef;
options = { rootMargin: '0px', threshold: 0.5, root: null }
observer: IntersectionObserver = new IntersectionObserver(this.handleObserver.bind(this), this.options);
MAXIMUM_DATA = 100;

We need to control the element list that has template references. We can use ViewChild decorator.

Next, we need to define the configuration for the default intersection observer on the variable options.

Then, define variable observer with an instance of class IntersectionObserver with two parameters, the first one is for callback and the rest for options.

After that, let’s create the function handleObserver. I use bind on the function handleObserver cause the inside of the function needs to access the data outside of the function, if you don’t use bind it only can access the data inside of the class IntersectionObserver.

  handleObserver(entries: any[]) {
entries.forEach(entry => {
const {
boundingClientRect,
intersectionRatio,
intersectionRect,
isIntersecting,
rootBounds,
target,
time
} = entry;
console.log(entry)
if (isIntersecting) {
if (this.userList.length < this.MAXIMUM_DATA) {
this.info = {
...this.info,
page: (this.info?.page || 0) + 1
}
this.getRandomUser();
}
}
})

};

So, on the function handleObserver, we need to get the first array cause the response of the callback intersection observer on the array. Then I get the key isIntersecting which means the user interacts until the bottom of the bar. Then, I added some logic if the total number of users is less than the maximum data, in this case, I hard coded to 100 data. So, it will update the parameters of the page or pagination and call the API to get random users. Then, it will re-fetch the API with a new payload.

Finally, update the function getRandomUser. I just updated the payload. Previously, I hardcoded the payload, but currently, I want to more dynamically, So I use the data from info which is the metadata of API like total, results, page, etc.

Also, don’t forget to update the user list, I use the function spread on the array. So, I copy the old data and then merge it with new data.

  getRandomUser(): void {
const payload: Payload = {
results: this.info?.results || 10,
page: this.info?.page || 1
};
this.randomUserService.getRandomUser(payload).subscribe({
next: ({info, results}) => {
this.info = info;
this.userList = [...this.userList, ...results];
},
error: (err: HttpErrorResponse) => {
throw new Error(err.message);
}
})
}

The results will be like this.

Demo Intersection Observer

Conclusion

You can use an intersection observer to create your infinite scroll feature, or a lazy load of some element, that will be fine. The advantage of this feature is better performance. Also, you don’t need to install a third-party library if only want to create a simple infinite scroll pagination.

--

--

No responses yet