Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observable<Object[]> - Can I skip/take?

I want to imlpement infinite-scrolling on my list of objects, they are Observable<Object[]>. Transferring them to promise and awaiting 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>
like image 270
Stan Avatar asked Oct 17 '25 05:10

Stan


2 Answers

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.

like image 169
Chrillewoodz Avatar answered Oct 18 '25 22:10

Chrillewoodz


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.

HTML

<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>

CSS

.scrollBarClass {
  max-height: 500px;
  overflow-y: scroll;
}

Angular component

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);
                });
    }
}

Angular service

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 });
}

WebAPI controller

[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);
    }
}

WebAPI service

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;
}
like image 22
Joe Belladonna Avatar answered Oct 19 '25 00:10

Joe Belladonna



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!