Search code examples
angularangular-materialangular-material-table

How to jump/scroll to a row in material table within a scrollable container?


I have something working if the table is fully displayed in the page, with only the page scrollbar. Using scrollIntoView.
But it does not work anymore if my table is inside a container with a fixed height and a scrollbar of its own.

The function I had:

onBeamIdInputChange(id: string) {
  const el = document.getElementById(id);
  if (el) {
    el.scrollIntoView({behavior: 'smooth', block: 'center'});
  }
}

With this in the template:

<td mat-cell *matCellDef="let i = index"> <span [id]="i"></span>{{ i }} </td>

But I need to keep a header div and the table-header always visible. So I tried a fixed height for the container, but then the jump to a specific row does not work anymore, it will just center on my container.

My html is more or less like this:

<div>Content to keep always visible - <input matInput type="text" ...>Jump to id</input></div>
<div id="container" style="height: 500px; overflow: auto;">
  <table mat-table> <!-- With sticky header -->
    ...
    <td mat-cell *matCellDef="let i = index"> <span [id]="i"></span>{{ i }} </td>
    ...
  </table>
</div>

How can I jump to the cell with such a nested scroll-view?

I tried https://stackoverflow.com/a/20958953/6165833 setting element's parent's scrollTop to element's own offsetTop. But the parent is <ng-container matColumnDef="id"> and not the view container with the scroll-area, so it does not work, and it also seems a little dirty for an angular component.

EDIT: At some point I had it working partially, scrolling automatically the nested view too (the container) but only if I had scrolled the main page before. So it'd work once and then no more, if I scrolled again on the main page, it'd work again just once etc.

It's like if my page was already centered on my container the call to scrollIntoView did not work, but if it had to center on my container then it would also scroll inside the container to display the searched element.

This is not happening anymore and I have no clue why not...


Solution

  • With a suggestion I got something cleaner than my previous solution, using angular to retrieve the elements and not depending on .parentElement calls.

    @ViewChild('tableDiv') tableDiv: ElementRef;
    @ViewChildren('idElement') idElements: QueryList<ElementRef>;
    
    onIdInputChange(id: string) {
      const foundElement = this.idElements.toArray().find(item =>
        item.nativeElement.id === id).nativeElement;
      if (foundElement) {
        // We ignore the header + two rows
        const offset = foundElement.offsetTop - (foundElement.offsetHeight * 3);
        this.tableDiv.nativeElement.scrollTo({ top: offset, left: 0, behavior: 'smooth' });
      }
    }
    

    <div #tableDiv>
      <table mat-table [dataSource]="myData">
        <ng-container matColumnDef="id">
          <th mat-header-cell *matHeaderCellDef> ID </th>
          <td mat-cell *matCellDef="let dataRow" #idElement [id]="dataRow.id"> 
            { dataRow.id }}
          </td>
        </ng-container>
        ...