CngxRecyclerPlaceholder
projects/common/data/recycler/recycler-placeholder.directive.ts
Import#
import { CngxRecyclerPlaceholder } from '@cngx/common/data'
Description#
Paints a row-aligned skeleton background under a virtualized scroll region so
a fast fling or teleport jump reveals placeholder bars instead of the bare
panel/page surface for the single frame the rendered rows lag scrollTop.
The mechanism is a static CSS layer, not a reactive template: by the time a
@for over "uncovered indices" could render, change detection has already
produced the real rows, so the lag frame is never painted reactively. A
background on the offset element always paints and also covers teleport
jumps no reactive window can, at zero per-frame cost.
Host it on the offset spacer element(s) - the empty divs (or presentation
<li>s) whose height reserves offsetBefore / offsetAfter - one directive
instance per spacer. Do NOT host it on a padding-blocked content element
that also contains the rows: a background paints across the whole padding box,
so it would show through behind every rendered row, not just in the gap.
The spacers carry no rows, so the layer is only ever visible where the window
has not caught up.
The repeat interval keys off a single representative row height: bind the
recycler to read its rowSizeHint, or pass an explicit [rowHeight]
fallback so the directive is usable standalone. Visuals are driven by
--cngx-recycler-placeholder-* CSS custom properties (see
@cngx/common/theming/components/cngx-recycler-placeholder.css); the shimmer
drops under prefers-reduced-motion via a spec-observable host class.
One instance per offset spacer, keyed off the recycler's rhythm
<ul>
@if (recycler.offsetBefore(); as before) {
<li role="presentation" aria-hidden="true"
[cngxRecyclerPlaceholder]="recycler" [style.height.px]="before"></li>
}
@for (item of visibleItems(); track item.id) { <li>...</li> }
@if (recycler.offsetAfter(); as after) {
<li role="presentation" aria-hidden="true"
[cngxRecyclerPlaceholder]="recycler" [style.height.px]="after"></li>
}
</ul>Standalone with an explicit row height
<div cngxRecyclerPlaceholder [rowHeight]="48"
[style.height.px]="recycler.offsetBefore()"></div>Metadata#
Host#
Index#
Inputs#
The recycler whose rowSizeHint sets the placeholder repeat rhythm.
Optional: the directive also works from an explicit [rowHeight], so this
follows the bridge-input shape (optional + empty-string transform) rather
than input.required - a bare cngxRecyclerPlaceholder attribute paired
with [rowHeight] is a valid, unbound usage.
undefined, { alias: 'cngxRecyclerPlaceholder', transform: (value) => (typeof value === 'string' ? undefined : value), }number | undefinedExplicit row height (px) used when no recycler is bound, or as a fallback.
HostBindings#
| Binding | Expression |
|---|---|
[class.cngx-recycler-placeholder--shimmer] | showShimmer() |
[style.--cngx-recycler-placeholder-row-height] | rowHeightVar() |