diprokon / ng-table-virtual-scroll

Virtual Scroll for Angular Material Table
https://diprokon.github.io/ng-table-virtual-scroll
MIT License
139 stars 42 forks source link

Observe changes in dataSource input of mat-table component #2

Closed yroc92 closed 4 years ago

yroc92 commented 4 years ago

Often there is a use case for when the dataSource input parameter is not a static value. Currently, if we pass a new object to dataSource after initialization of the view, then the tvsItemSize directive fails to display any table rows because it was connected to the previous instance (see ngAfterViewInit method in TableItemSizeDirective). Any new instances of dataSource will not be connected to since we do not observe changes.

Proposal: use MutationObserver web api as seen here: https://nitayneeman.com/posts/listening-to-dom-changes-using-mutationobserver-in-angular/

The tvsItemSize directive should do something like the following:


ngAfterViewInit() {
   const element = this.table;

   const changes = new MutationObserver((mutations: MutationRecord[]) => {
      mutations.forEach((mutation: MutationRecord) => this.connectToDataSource(mutation)
   });

   changes.observe(element, {
       attributes: true,
    });

   this.scrollStrategy.stickyChange
      .pipe(
        filter(() => this.isStickyEnabled()),
        takeWhile(this.isAlive())
      )
      .subscribe((stickyOffset) => {
        this.setSticky(stickyOffset);
      });
}

private connectToDataSource(mutation: MutationRecord) {
    if (this.table.dataSource instanceof TableVirtualScrollDataSource) {
      const dataSource = this.table.dataSource;
      this.scrollStrategy.renderedRangeStream
        .pipe(
          takeUntil(newDataSource), // need to unsubscribe when new datasource comes in
          takeWhile(this.isAlive())
        )
        .subscribe(range => {
          this.zone.run(() => {
            dataSource.renderedRangeStream.next(range);
          });
        });
      dataSource.connect()
        .pipe(
          map(() => dataSource.data.length),
          distinctUntilChanged(),
          takeUntil(newDataSource), // need to unsubscribe when new datasource comes in
          takeWhile(this.isAlive())
        )
        .subscribe((length) => {
          this.scrollStrategy.dataLength = length;
        });
    } else {
      throw new Error('[tvsItemSize] requires TableVirtualScrollDataSource be set as [dataSource] of [mat-table]');
    }
}
diprokon commented 4 years ago

Hi! Thanks for proposal. I think, I found better way to check it. Please, look at my last commit - if You think it's ok, please, let me know

yroc92 commented 4 years ago

It actually did not work for me. Are you able to pass in a new instance of TableVirtualScrollDataSource to the table's dataSource any time after init and it still renders?

diprokon commented 4 years ago

Yes. I even add a test for this case. Can you check it again? If it's not working, can you provide example to reproduce?

diprokon commented 4 years ago

Hi @yroc92 ! Did you try the last version?