I want to imlpement infinite-scrolling on my list of objects, they are Observable<Object[]>
. Transferring them to promise and await
ing them is not an option since they need to be updated in realtime.
What I did is used .map()
which kind-of works, but the problem is that angular is re-rendering the whole list whenever I take 20
items instead of 10
for example.
One solution would be to actually skip first 10
and load next 10
which would only add new elements to the page. However I'm not sure if it is possible?
Here is what I have
Typescript
// Every time this happens page goes to top
// because all the items are re-rendered, not only new ones
public onScroll(): void {
this.take += 10; // on ngOnInit() take is set to 10.
this.sales = this.filteringObservable.map(data => {
return _.take(data, this.take);
});
}
HTML
<div class="sales-container" infiniteScroll [infiniteScrollDistance]="2" [infiniteScrollThrottle]="50" (scrolled)="this.onScroll()">
<app-sales-item *ngFor="let sale of this.sales | async" [sale]="sale"></app-sales-item>
</div>
This is very easy to achieve, I have multiple lists which acts like this.
First of all you want to use some sort of lib, I use ngx-infinite-scroll.
Then you want your list to use it:
<ul infiniteScroll (scrolled)="getItems()">
<li *ngFor="let item of items; trackBy:trackByFn">{{item}}</li>
</ul>
trackByFn(i: number, item) {
return item.<unique-key>;
}
Next you want to react to the scroll, do this via the scrolled
listener as shown above.
Now, what you need for an infinite list to work properly is for each item to have a unique key. You also need the total amount of items available for the list, and the number of items that are currently being shown.
Here is a function I use for this, if you're not using an API to fetch more results you can tweak it to your liking.
// isReset decides whether to set offset to 0 or not
getItems(isReset = false): void {
if (!isReset && this.items.length >= this.total) {
return;
}
this.api.get<CustomResponse>('my-api-endpoint', {
limit: 10,
offset: isReset ? 0 : this.items.length,
})
.pipe(
first(),
tap((res) => this.total = res.totalsize || 0),
map((res) => res.list)
)
.subscribe((items) => {
// This bit prevents the same batch to be loaded into the list twice if you scroll too fast
if (this.items.length && !isReset) {
if (items[items.length - 1].<unique-key> === this.items[this.items.length - 1].<unique-key>) {
return;
}
}
this.items = isReset ? items : [...this.items, ...items];
})
);
}
Make sure to replace <unique-key>
with a unique key of each item.
I hope this helps.
This is how we are doing this using Angular 5 and WebApi as server side. We have implemented some kind of sorting in our tables but you can use just part with scrolling and use it on your list. Also we are taking all data from tables and then sending it to clients in chuncks of 10, but if you need speed, you can page your data in SQL server and take only 10 rows at a time. If you need logic for that kind of paging just let me know.
<div #scb id="scb" class="scrollBarClass" *ngIf="sales && sales.length" (scroll)="onScroll()">
<div class="table table-striped table-responsive table-sm table-bordered">
<table class="table" *ngIf="sales && sales.length">
// here goes you table structure, headers, body and so...
</table>
</div>
</div>
.scrollBarClass {
max-height: 500px;
overflow-y: scroll;
}
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
@ViewChild('scb') grid: ElementRef;
scroll = false;
page = 1;
total = 0;
onScroll() {
if (this.grid.nativeElement.scrollHeight - this.grid.nativeElement.scrollTop < 510 && this.scroll == false) {
if (this.sales.length == this.total) {
return;
}
let p: any;
this.page++;
this.scroll = true;
this._myService.searchSales(this.page, 10, this.sortType, this.sortAD)
.subscribe(
(data) => { p = data['data']; this.total = data['count']},
error => { this.errorMessage = <any>error; this.page--; },
() => {
this.scroll = false;
Array.prototype.push.apply(this.sales, p);
});
}
}
searchSales(page: number, recperpage: number, sorttype: string, sortad: string) {
let params = new HttpParams()
.append('page', page.toString())
.append('recperpage', recperpage.toString())
.append('sorttype', sorttype)
.append('sortad', sortad);
return this._http.get<any[]>('sales/searchsales', { params: params });
}
[HttpGet]
public IHttpActionResult searchsales(int page, int recperpage, string sorttype, string sortad)
{
try
{
var count = 0;
var r = _salesService.SearchSales(sorttype, sortad, ref count);
return Ok(new { data=r.Skip((page - 1) * recperpage).Take(recperpage).ToList(), count });
}
catch (Exception e)
{
return InternalServerError(e);
}
}
public List<Sales> SearchSales(string sorttype, string sortad, ref int count)
{
var query = "";
query = " select * FROM Sales "+
" ORDER BY " + ((sorttype == null) ? "DateOfSale" : sorttype) + " " + ((sortad == null) ? "DESC" : sortad);
var result = SQLHelper.ExecuteReader(query);
count = result.Count;
return result;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With