Angular lazy load data based on scroll (16 Steps)

Photo by shiyang xu on Unsplash

Angular lazy load data based on scroll (16 Steps)

This project will describe lazy load data based on the scroll.

Here the lazy load means we can’t dump data at once. Either we will load data based on an event (on scroll, paginator)in that way the user won’t wait for the entire response. Instead, just user needs to perform the event in the way the data will be updated on UI.

Please follow the below steps to implement the lazy load data concept inside angular.

Step 1. Create an angular project with the below command.

ng new angular-lazy-loading-data

Step 2. After the successful creation of an angular app, change the file directory to project-name. “cd angular-lazy-loading-data”.

Open the project in vs code using “code .” in the terminal or open with vs code. Then run the project using “ng serve” in a terminal. Open project in chrome using localhost:4200.

Step 3. Open the app component in vs code and remove the content which is created by Angular CLI while creating the app.

Step 4. Create audits service under the app/apis folder by using the below command.

ng generate service audits

Step 5. After that open app.module.ts and import HttpClientModule and add it to the imports array like below.

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
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 { }

Step 6. After that open audits.service.ts and import HttpClient, add it as a dependency inside the constructor.

import { HttpClient } from '@angular/common/http';  
import { Injectable } from '@angular/core';

@Injectable({  
  providedIn: 'root'  
})

export class AuditsService{  
   constructor(private http:HttpClient) { } 
}

Step 7. Add the getAudits method to call the audit service and headers variable inside audits.service.ts to send headers while calling the service.

import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';  
import { Injectable } from '@angular/core';

@Injectable({  
  providedIn: 'root'  
})  
export class AuditsService {

public  baseUrl = "https://letstalk-be.herokuapp.com" || "http://localhost:3000)"

public headers:HttpHeaders= new HttpHeaders({  
    'Content-Type':'application/json',  
    'Accept':"application/json",  
    'Access-Control-Allow-Methods':'GET,POST,PUT,DELETE',  
    'Authorization':''  
});

constructor(private http:HttpClient) { }

public getAudits(pageIndex,pageSize){  
    let params= new HttpParams();  
    return this.http.get(`${this.baseUrl}/public/audits/${pageIndex}/${pageSize}`,{headers :this.headers,params})  
  }

}

Step 8. Import audits service inside app.component.ts and add it as a dependency inside the constructor. And also create variables

  • audits to store audit information.
  • totalCount to store the audits count.
  • pageIndex to store the index value e.g. 0,1 etc.
  • pageSize to store the pageSize e.g. 10,20 etc.
import { Component, OnInit} from '@angular/core")';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits: any[]= [];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

  constructor(private AuditsService: AuditsService) { }

  ngOnInit(){}

}

Step 9. Create getAudits() to call the API to get the audit information and after receiving the response from the service store the response into audits, totalCount. And also call the getAudits() method from ngOnInit(). Also updates the pageIndex after calling the getAudits() method.

import { Component, OnInit} from '@angular/core';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits:any[]= [];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

constructor(private auditsService: AuditsService) { }

public async ngOnInit(): Promise<void> {  
    await this.getAudits(this.pageIndex,this.pageSize);  
    this.pageIndex +=1;  
}

public async getAudits(pageIndex,pageSize){  
    try {  
      const response:any= await   this.auditsService.getAudits(pageIndex,pageSize).toPromise();  
      this.audits = [...this.audits,...response.audits]  
      this.totalCount = response.totalCount;  
    } catch (error) {  
      console.log(error)  
    }  
}

}

In the above code, if you observed one line, I think you will get a little confused about why am updating the audits instead of assigning them. In the lazy load process, we need to maintain previous data.

this.audits = [...this.audits,...response.audits];

Step 10. After that open app.component.html and the below code to display the audits.

<div class="container-fluid">  
  <h3 class="mt-2 mb-1">List of audits | Lazy loading   
    <span style="font-size: 12px;">  
        <strong>({{audits.length}} of {{totalCount}})</strong>  
    </span>  
  </h3>  
  <ul class="list-group mt-3"   
 style="overflow-y: auto;max-height:400px;" >  
    <li class="list-group-item" *ngFor="let audit of audits">  
      <span ><strong>{{audit.email ? audit.email : 'No Email entered'}}</strong></span>  
      <span >{{audit.statusCode === 200 ? 'Login success' : 'Login Failed'}}</span>  
    </li>  
  </ul>  
</div>

Step 11. After that run the project by using “ng serve” and open the project in a browser by using localhost:4200. Check the changes in UI, If any errors, please go the all the steps once again.

Step 12. But we have not added any code related to lazy loading, For That, I am using the ElementRef concept in angular.

  • Open app.component.ts and create onScrollLoadData().
import { Component, OnInit} from '@angular/core';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits:any[]= [];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

constructor(private auditsService: AuditsService) { }

public async ngOnInit(): Promise<void> {  
    await this.getAudits(this.pageIndex,this.pageSize);  
    this.pageIndex +=1;  
  }

public async getAudits(pageIndex,pageSize){  
    try {  
      const response:any= await this.auditsService.getAudits(pageIndex,pageSize).toPromise();  
      this.audits = [...this.audits,...response.audits]  
      this.totalCount = response.totalCount;  
    } catch (error) {  
      console.log(error)  
    }  
  }

public onScrollLoadData(){}

}
  • Open app.component.html and add on scroll event in the ul element. Then only we can be able to call the onScrollLoadData() method while scrolling.
<div class="container-fluid">  
  <h3 class="mt-2 mb-1">List of audits | Lazy loading   
    <span style="font-size: 12px;">  
        <strong>({{audits.length}} of {{totalCount}})</strong>  
    </span>  
  </h3>  
  <ul class="list-group mt-3"   
 style="overflow-y: auto;max-height:400px;"  
  (scroll)="onScrollLoadData()">  
    <li class="list-group-item" *ngFor="let audit of audits">  
      <span ><strong>{{audit.email ? audit.email : 'No Email entered'}}</strong></span>  
      <span >{{audit.statusCode === 200 ? 'Login success' : 'Login Failed'}}</span>  
    </li>  
  </ul>  
</div>

Step 13. Based on the scroll event, we need to call the API to get the data from the service for that add the below code in app.component.ts.

import { Component, OnInit} from '@angular/core';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits:any[ ]= [ ];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

constructor(private auditsService: AuditsService) { }

public async ngOnInit(): Promise<void> {  
    await this.getAudits(this.pageIndex,this.pageSize);  
    this.pageIndex +=1;  
  }

public async getAudits(pageIndex,pageSize){  
    try {  
      const response:any= await this.auditsService.getAudits(pageIndex,pageSize).toPromise();  
      this.audits = [...this.audits,...response.audits]  
      this.totalCount = response.totalCount;  
    } catch (error) {  
      console.log(error)  
    }  
  }

public async onScrollLoadData(){  
    if( this.audits.length !== this.totalCount){  
      await this.getAudits(this.pageIndex, this.pageSize);  
      this.pageIndex +=1;  
    }  
}

Step 14. Oops !!! After adding all the above changes able to see the data will be loaded after the scroll. But I observed when a little scroll the apis are calling continuously. This is not the correct way we need to call the API when the scroll bar reached to bottom.

Step 15. For Achieving the above point, need to follow the below steps.

  • Open app.component.html and add template variable inside ul tag to get the ul entire element in app.component.ts.
<div class="container-fluid">  
  <h3 class="mt-2 mb-1">List of audits | Lazy loading   
    <span style="font-size: 12px;">  
        <strong>({{audits.length}} of {{totalCount}})</strong>  
    </span>  
  </h3>  
  <ul class="list-group mt-3"   
  #uiElement style="overflow-y: auto;max-height:400px;"  
  (scroll)="onScrollLoadData()" >  
    <li class="list-group-item" *ngFor="let audit of audits">  
      <span ><strong>{{audit.email ? audit.email : 'No Email entered'}}</strong></span>  
      <span >{{audit.statusCode === 200 ? 'Login success' : 'Login Failed'}}</span>  
    </li>  
  </ul>  
</div>
  • Open app.component.ts and import Viewchild, and ElementRef to get scroller height. Create uiElement variable and give type as ElementRef, Add @ViewChild directive on top of uiElement and add view child property as template variable which you added inside HTML i.e. uiElement like below.
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits:any[]= [];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

@ViewChild('uiElement', { static: false }) public uiElement: ElementRef;**

constructor(private auditsService: AuditsService) { }

public async ngOnInit(): Promise<void> {  
    await this.getAudits(this.pageIndex,this.pageSize);  
    this.pageIndex +=1;  
  }

public async getAudits(pageIndex,pageSize){  
    try {  
      const response:any= await this.auditsService.getAudits(pageIndex,pageSize).toPromise();  
      this.audits = [...this.audits,...response.audits]  
      this.totalCount = response.totalCount;  
    } catch (error) {  
      console.log(error)  
    }  
  }

public async onScrollLoadData(){  
    if(this.audits.length !== this.totalCount){  
      await this.getAudits(this.pageIndex, this.pageSize);  
      this.pageIndex +=1;  
    }  
  }

}

Step 16. Finally after adding all the above points, Add the below logic to load data when the scroll bar is reached to bottom. For that am using the element ref variable uiElement.

Here is the formula for checking the scroll bar position whether it is on the bottom or not.

if(clientHeight + Math.round(scrollTop) === scrollHeight){  
    // Add your logic here.  
}

Just add the below lines of code which is high lighted in bold to load the data when the scroll bar is at the bottom position.

import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';  
import { AuditsService } from './apis/audits.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.scss']  
})  
export class AppComponent implements OnInit{  

  public audits:any[ ]= [ ];  
  public totalCount=0;  
  public pageIndex=0;  
  public pageSize=10;

@ViewChild('uiElement', { static: false }) public uiElement: ElementRef;

constructor(private auditsService: AuditsService) { }

public async ngOnInit(): Promise<void> {  
    await this.getAudits(this.pageIndex,this.pageSize);  
    this.pageIndex +=1;  
  }

public async getAudits(pageIndex,pageSize){  
    try {  
      const response:any= await this.auditsService.getAudits(pageIndex,pageSize).toPromise();  
      this.audits = [...this.audits,...response.audits]  
      this.totalCount = response.totalCount;  
    } catch (error) {  
      console.log(error)  
    }  
  }

public async onScrollLoadData(){  
   const nativeElement= this.uiElement.nativeElement  
    console.log(this.uiElement)  
    if(nativeElement.clientHeight + Math.round(nativeElement.scrollTop) === nativeElement.scrollHeight  &&  this.audits.length !== this.totalCount){  
      await this.getAudits(this.pageIndex, this.pageSize);  
      this.pageIndex +=1;  
    }
  }  
}

Check the output in the Chrome browser. If you have any concerns please add them in the comments.

Output.

lazyload.gif

Source Code

Front End:
GitHub:
angular-lazy-loading-data

Back End:
GITHUB:
Letstalk-Backend

Live Backend URI: letstalk-be.herokuapp.com

Stack blitz Project Preview: angular-lazy-load-data

Thanks for reading my article, Please share your feedback, claps, and comments. In that way, it will be helped me to improve my articles in the future. Please share my story with your near and dear

https://www.buymeacoffee.com/mryenagandula