0
votes

I really want to incorporate the cdk-virtual-scroll. But with cdk-virtual-scroll table headers wont follow css sticky position property. So I came across a post describing custom scroll strategy.

https://stackblitz.com/edit/cdk-virtual-table-sticky-header?file=src%2Fapp%2Fapp.component.ts

In this example they extends the datasource.

Here they have hardcoded data.

  rows = Array(200).fill(0).map((x, i) => {
    return {name: 'name' + i, id: i, age: 27};
  });

Is there a way to get data from an http call and populate dataSource with the observable & unwrap those observables using async pipe in the template. (rather than hardcoding data in the component.ts file or subscribing to the data in componenet.ts file to achieve custom-scroll like the one above)?

Make http call to this end point :

https://jsonplaceholder.typicode.com/users

Like this:

[dataSource]="dataSource | async"

1

1 Answers

1
votes

The table gets the data as an Observable from the DataSource by calling dataSource.connect. This function should return an Observable that emits the data to display. You don't need the AsyncPipe when using a DataSource.

Pass the initial data as an Observable to your DataSource and modify this data with RxJS operators in dataSource.connect before returning it. Terminate the Observable in dataSource.disconnect.

Based on the stackblitz you provided this could be:

const PAGESIZE = 20;
const ROW_HEIGHT = 48;

export class GridTableDataSource extends DataSource<any> {
  private initialData$: Observable<any[]>;
  private onDisconnect$ = new Subject<void>();

  offset = 0;
  offsetChange = new BehaviorSubject(0);

  constructor(
    initialData$: Observable<any[]>,
    private viewport: CdkVirtualScrollViewport, 
    private itemSize: number
  ) {
    super();
    this.initialData$ = initialData$;
    this.viewport.scrollToOffset(0);
  }

  connect(collectionViewer: CollectionViewer): Observable<any[] | ReadonlyArray<any>> {
    return combineLatest( // combine the scroll events with your initial data
      this.viewport.elementScrolled().pipe( // start with a currentTarget
        startWith({ currentTarget: this.viewport.getElementRef().nativeElement })
      ),
      this.initialData$.pipe(
        tap(data => this.viewport.setTotalContentSize(this.itemSize * data.length))
      )
    ).pipe(
      map(([event, initialData]: [any, any[]]) => { // map to visible data
        const start = Math.floor(event.currentTarget.scrollTop / ROW_HEIGHT);
        const prevExtraData = start > 5 ? 5 : 0;
        // const prevExtraData = 0;
        const slicedData = initialData.slice(
          start - prevExtraData,
          start + (PAGESIZE - prevExtraData)
        );
        this.offset = ROW_HEIGHT * (start - prevExtraData);
        this.viewport.setRenderedContentOffset(this.offset);
        this.offsetChange.next(this.offset);
        return slicedData;
      }),
      takeUntil(this.onDisconnect$)  // unsubscribe on disconnect
    );
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.onDisconnect$.next();
  }
}

Pass the http request as initial data to your custom DataSource.

this.dataSource = new GridTableDataSource(
  this.http.get<any[]>("https://jsonplaceholder.typicode.com/todos"),
  this.viewport,
  this.itemSize
);

https://stackblitz.com/edit/cdk-virtual-table-sticky-header-26g3j5