1

I am facing an odd issue with a PrimeNG 9 TreeTable in an application I am working on which I have configured virtual scrolling and a nested node structure with toggle\expand behaviour.

I am finding after I make a selection, the highlighted node is lost when either scrolling or expanding another node. The highlighted selection jumps to another random node.

Minimal reproducible demo can be found here: https://stackblitz.com/edit/primeng9-treetablescroll-selections

I was wondering if this may be to do with the this.cdr.detectChanges(); which I added in the ngAfterViewInit lifecycle hook, which I added to bring in this bug fix in a v10.0.3 release but removing this makes no difference and brings back an ExpressionChangedAfterItHasBeenCheckedError which the bug fix addresses.

The application cannot be migrated to PrimeNG 10 yet either, so I am specifically looking for a v9 fix\workaround if possible.

Anyone got any ideas what may be going on here?

SOLVED

Kudos to @DipenShah for pointing me in the right direction to find a suitable workaround. Based on his answer I enhanced his approach a little further to handle the case that in v9 the tree table fires the expand\collapse events AND the node select event when toggling the parent nodes. This does not happen in v10.

My final workaround is here https://stackblitz.com/edit/primeng9-treetablescroll-selections-utsj2p?file=src/app/app.component.ts

Antikhippe
  • 6,316
  • 2
  • 28
  • 43
mindparse
  • 6,115
  • 27
  • 90
  • 191
  • In your example, I can't see any code. – StPaulis Oct 26 '20 at 11:49
  • @StPaulis - my bad, demo link updated, thanks – mindparse Oct 26 '20 at 11:57
  • Not sure whats going on with that version you're using but it seems buggy.. the link you provide also links to https://primeng9-treetablescroll-selections.stackblitz.io/ for a full page demo. I notice the last element is supposed to be 999 (and it is initially) but when I expand/collapse a row or two row 994 ends up at the end somehow. My immediate thoughts would be this is buggy, don't use it. – Rob Evans Oct 26 '20 at 12:21
  • @RobEvans - if this wasnt buggy, I wouldnt be asking this question in the first place! – mindparse Oct 26 '20 at 12:29
  • fair enough :) personally I'd find another library to use that isn't apparently broken to begin with – Rob Evans Oct 26 '20 at 13:03

1 Answers1

1

There seems to be an issue with the library itself and with recycling views before reusing them during scrolling.

Fortunately, if you want to take things in to your hands, by manually adding highlighted class you can fix the issue. Take a look at updated stackblitz.

app.component.ts

import {AfterViewInit, ChangeDetectorRef,  Component,OnDestroy,OnInit, ViewChild} from '@angular/core';
import {NodeService} from './nodeservice';
import {TreeNode} from 'primeng/api';
// import { PrimeNGConfig } from 'primeng/api';
import { TreeTable } from 'primeng/treetable';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit, OnDestroy { 
    
    @ViewChild(TreeTable)
    private virtualTable: TreeTable;

    virtualFiles: TreeNode[];
    selectedNode: TreeNode;

    cols: any[];
  
    private virtualTableSub: Subscription;

    constructor(private cdr: ChangeDetectorRef) { }

    ngOnInit() {
        this.virtualFiles = Array.from({length: 1000}).map((_,i) => this.createNode(i, 100));

        this.cols = [
            { field: 'name', header: 'Name' },
            { field: 'size', header: 'Size' },
            { field: 'type', header: 'Type' }
        ];
    }

    expanded(e) {
      this.selectedNode = null;
      setTimeout(() => {
        this.selectedNode = e.node;
        this.cdr.detectChanges();
      });
    }

    collapsed(e) {
      this.selectedNode = null;
      setTimeout(() => {
        this.selectedNode = e.node;
        this.cdr.detectChanges();
      });
    }

    ngAfterViewInit() {
      this.virtualTableSub = this.virtualTable.tableService.uiUpdateSource$.subscribe(() => {
        if (this.virtualTable.virtualScroll) {
          this.cdr.detectChanges();
        }
      });
    }

    ngOnDestroy() {
      this.virtualTableSub?.unsubscribe();
    }

    createNode(i: number, children: number): TreeNode {
        let node: TreeNode = {
            data: {name: 'Node ' + i, type: 'virtual node', size: Math.ceil(Math.random() * 10000) + 'kb'},
            children: Array.from({length: children}).map((_,j) => {
                return { 
                    data: {name: 'Node ' + i + '.' + j, type: 'virtual child node', size: Math.ceil(Math.random() * 10000) + 'kb'}
                }
            })
        };

        return node;
    }
}

app.component.html

<div class="card">
    <h5>Full Page Scroll</h5>
    <p>FlexScroll can also be used for cases where scrollable viewport should be responsive with respect to the window
        size. See the <a [routerLink]="['/treetable/flexscroll']">Full Page</a> demo for an example.</p>

    <h5>Virtual Scroll with 100000 Nodes</h5>
    <p-treeTable #treeTable [value]="virtualFiles" [columns]="cols" [scrollable]="true" [rows]="100"
        selectionMode="single" [(selection)]="selectedNode" (onNodeExpand)="expanded($event)"
        (onNodeCollapse)="collapsed($event)" scrollHeight="200px" [virtualScroll]="true" [virtualRowHeight]="34"
        dataKey="name">
        <ng-template pTemplate="header" let-columns>
            <tr>
                <th *ngFor="let col of columns">
                    {{col.header}}
                </th>
            </tr>
        </ng-template>
        <ng-template pTemplate="body" let-rowNode let-rowData="rowData" let-columns="columns">
            <tr [ttRow]="rowNode" [ttSelectableRow]="rowNode" style="height:34px"
                [ngClass]="{ 'highlighted' : selectedNode?.data === rowData }">
                <td *ngFor="let col of columns; let i = index">
                    <p-treeTableToggler [rowNode]="rowNode" *ngIf="i == 0"></p-treeTableToggler>
                    {{rowData[col.field]}}
                </td>
            </tr>
        </ng-template>
    </p-treeTable>
</div>

app.component.scss

::ng-deep.ui-treetable .ui-treetable-tbody > tr.highlighted {
  background-color: #57a0d7;
  color: #fff;
}

I would encourage you to open an issue on the library's github repository.

Dipen Shah
  • 25,562
  • 1
  • 32
  • 58
  • Thanks for the response and providing a potential solution, this certainly looks like a direction I could take. However there is an unpleasant flicker when the re-selection\re-highlighting is occurring – mindparse Oct 26 '20 at 17:11
  • 1
    @mindparse I did see that as well and it is something related to library for some reason when row is expanded of collapsed you will see selected event is fired if tap in to `onNodeSelect` event, not sure why but without reveres engineering it will be hard to point out but that is definitely library issue. – Dipen Shah Oct 26 '20 at 17:17
  • Seems like both treetabletoggler and treetable are listening to click event and sending expanded/collapsed and selected events respectively on node click and hence the issue. You will see jumping selection behavior if you do not updated selected node in excpanded/collased event handlers. – Dipen Shah Oct 26 '20 at 17:31
  • Hey man, your suggested workaround helped me figure out how to handle that flickering of the setting of the selected node to null and back again. See my updated question with link to new stackblitz. The issue is that in v9 the TreeTable fires a node selection event AND an expand\collapse event when you toggle a parent node. This is working great for me now and will be keep us going on our project until we migrate to v10. – mindparse Oct 27 '20 at 12:37