Skip to content

fix(treetable): sync frozen columns vertically when they arrive after init - #1407

Open
rene-schakmann wants to merge 1 commit into
openng-org:mainfrom
rene-schakmann:fix/treetable-frozen-vertical-scroll
Open

fix(treetable): sync frozen columns vertically when they arrive after init#1407
rene-schakmann wants to merge 1 commit into
openng-org:mainfrom
rene-schakmann:fix/treetable-frozen-vertical-scroll

Conversation

@rene-schakmann

Copy link
Copy Markdown

Defect Fix

Fixes #942

Problem

TTScrollableView resolved its frozen sibling exactly once, in ngAfterViewInit:

if (this.tt.frozenColumns || this.tt.frozenBodyTemplate || this.tt._frozenBodyTemplate) {
    addClass(this.el.nativeElement, 'p-treetable-unfrozen-view');
}

let frozenView = this.el.nativeElement.previousElementSibling;
if (frozenView) { /* ... findSingle ... */ }

The frozen view is rendered by *ngIf="frozenColumns || frozenBodyTemplate || _frozenBodyTemplate". When none of those are set on the first render — the ordinary case of frozenColumns bound to data that resolves asynchronously — the frozen view does not exist yet, so the unfrozen view initializes with:

  • frozenSiblingBody left undefined, and
  • the p-treetable-unfrozen-view class never added.

Neither is ever re-evaluated once the frozen view appears, which leaves the component permanently in a broken state:

  • Vertical scrolling does not sync. onBodyScroll guards on if (this.frozenSiblingBody), so the frozen rows never follow the scrollable rows. Horizontal scrolling still works because it does not go through that reference — matching the report exactly.
  • The layout breaks. .p-treetable-unfrozen-view { position: absolute; top: 0; } never applies, so the scrollable view stacks below the frozen view instead of beside it.

Measured on the docs demo with frozenColumns supplied asynchronously (Chrome, virtualScroll on, scrollHeight="250px"):

before after
unfrozen.scrollTop = 180frozen.scrollTop 0 180
p-treetable-unfrozen-view applied no yes
frozen / unfrozen view top 195 / 484 (stacked) equal (side by side)

This also explains the workarounds in the issue thread: every one of them re-attaches the scroll listener from the consumer's own ngAfterViewInit / ngAfterViewChecked.

Solution

Move the resolution into bindFrozenSiblingBody() and call it from ngAfterViewChecked as well as ngAfterViewInit, so a frozen view that appears later is picked up. The lookup short-circuits on frozenSiblingBody?.isConnected, so the steady state is a single property read per check — the querySelector only runs while the reference is missing or stale.

Removing the frozen columns again is handled symmetrically: the class is dropped and the reference released, so a view that stops being "unfrozen" does not keep a detached node alive.

The selector split between [data-pc-name="virtualscroller"] (virtual scroll) and [data-pc-section="scrollablebody"] is unchanged, and is now also re-evaluated if virtualScroll is toggled, since that swaps the body element out.

Changes

  • packages/optimus-ui/src/treetable/treetable.ts — extract bindFrozenSiblingBody(), call it from ngAfterViewChecked, release the reference and class when frozen columns are removed
  • packages/optimus-ui/src/treetable/treetable.spec.ts — 5 tests covering late-arriving frozen columns (virtual and non-virtual), the unfrozen class, scroll propagation, and teardown

Verification

ng test optimus-ui --include='**/treetable/*.spec.ts' → 179/179 passing (5 new). Full optimus-ui unit suite green.

Behaviour confirmed in Chrome against the docs TreeTable demo, before and after the change, for both scrollHeight="250px" and scrollHeight="flex", with and without virtualScroll. The pre-existing synchronous case (frozen columns or a #frozenbody template available on first render) was already working and is unchanged.

Breaking Changes

None.


🤖 Generated with Claude Code

… init

TTScrollableView resolved its frozen sibling once, in ngAfterViewInit. The
frozen view is rendered by `*ngIf="frozenColumns || frozenBodyTemplate ||
_frozenBodyTemplate"`, so when none of those are set on the first render —
the ordinary case of `frozenColumns` bound to asynchronously resolved data —
the unfrozen view initialised with `frozenSiblingBody` undefined and without
the `p-treetable-unfrozen-view` class, and neither was ever re-evaluated.

The result was a permanently broken component: `onBodyScroll` guards on
`frozenSiblingBody`, so the frozen rows never followed the scrollable rows
vertically, while horizontal scrolling kept working because it does not go
through that reference. The missing class also dropped
`position: absolute; top: 0`, stacking the scrollable view below the frozen
view instead of beside it.

Resolution now lives in `bindFrozenSiblingBody()` and runs from
ngAfterViewChecked as well, so a frozen view that shows up later is picked
up. The lookup short-circuits on a still-connected reference, keeping the
steady state to a single property read per check. Removing the frozen
columns again drops the class and releases the reference.

Fixes openng-org#942
@geromegrignon geromegrignon added this to the Catch-up milestone Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TreeTable: frozenColumns does not scroll vertically

2 participants