Skip to main content

Virtual scroll strategies

A RxVirtualScrollStrategy positions the views created by *rxVirtualFor inside the viewport. All three pre-packaged strategies position views absolutely and move them with CSS transforms, and share a common set of inputs. Exactly one strategy must be applied, via its attribute selector, to the <rx-virtual-scroll-viewport> element.

Why this matters: see How RxAngular virtual scrolling works and Concurrent scheduling & the frame budget.

Import

import { FixedSizeVirtualScrollStrategy, DynamicSizeVirtualScrollStrategy, AutoSizeVirtualScrollStrategy, RxVirtualScrollStrategy } from '@rx-angular/template/virtual-scrolling';

Shared inputs

Every pre-packaged strategy exposes these inputs.

InputTypeDefaultDescription
runwayItemsnumber10Number of items rendered upfront in scroll direction.
runwayItemsOppositenumber2Number of items rendered upfront in the opposite scroll direction.
appendOnlybooleanfalseKeep already-rendered views in the DOM after they scroll out of view. Reacts to changes; can be toggled at runtime.
keepScrolledIndexOnPrependbooleanfalseMaintain the currently scrolled index when new data is prepended. Used for reverse infinite scrollers.

FixedSizeVirtualScrollStrategy

Positions views based on a single fixed item size. Comparable to the CDK FixedSizeVirtualScrollStrategy, but with the high-performance layout technique.

  • Selector: rx-virtual-scroll-viewport[itemSize]
  • Class: FixedSizeVirtualScrollStrategy<T, U>
InputTypeDefaultDescription
itemSizenumber50Fixed size (in px) of every item, set via [itemSize].
import { Component } from '@angular/core';
import { FixedSizeVirtualScrollStrategy, RxVirtualScrollViewportComponent, RxVirtualFor } from '@rx-angular/template/virtual-scrolling';

@Component({
imports: [RxVirtualFor, FixedSizeVirtualScrollStrategy, RxVirtualScrollViewportComponent],
template: `
<rx-virtual-scroll-viewport [itemSize]="itemSize">
<div class="item" *rxVirtualFor="let item of items$">{{ item.content }}</div>
</rx-virtual-scroll-viewport>
`,
})
export class MyComponent {
itemSize = 50;
items$ = inject(DataService).getItems();
}

DynamicSizeVirtualScrollStrategy

Calculates each item's size from a user-provided function instead of reading the DOM, so it has better runtime performance than the autosize strategy when the sizes are known ahead of time.

  • Selector: rx-virtual-scroll-viewport[dynamic]
  • Class: DynamicSizeVirtualScrollStrategy<T, U>
InputTypeDefaultDescription
dynamic(item: T) => numberFunction returning the size (in px) of a given item, bound via [dynamic].
@Component({
imports: [RxVirtualFor, DynamicSizeVirtualScrollStrategy, RxVirtualScrollViewportComponent],
template: `
<rx-virtual-scroll-viewport [dynamic]="dynamicSize">
<div class="item" *rxVirtualFor="let item of items$">
{{ item.content }}
@if (item.description) {
<div>{{ item.description }}</div>
}
</div>
</rx-virtual-scroll-viewport>
`,
})
export class MyComponent {
// items with a description are 120px tall, others 50px
dynamicSize = (item: Item) => (item.description ? 120 : 50);
items$ = inject(DataService).getItems();
}

AutoSizeVirtualScrollStrategy

Renders and positions items based on their individual measured size, using a ResizeObserver to react to size changes. Comparable to the CDK experimental autosize strategy, but with better visual stability and a working scrollToIndex / scrolledIndexChange.

  • Selector: rx-virtual-scroll-viewport[autosize]
  • Class: AutoSizeVirtualScrollStrategy<T, U>
InputTypeDefaultDescription
tombstoneSizenumber50Anticipated size for not-yet-measured views, used to size the runway.
withResizeObserverbooleantrueObserve rendered views for size changes and re-position accordingly.
withSyncScrollbarbooleanfalseKeep the scrollbar synchronized during measurement.
resizeObserverConfig{ options?: ResizeObserverOptions; extractSize?: (entry: ResizeObserverEntry) => number; }Configuration forwarded to the internal ResizeObserver.
@Component({
imports: [RxVirtualFor, AutoSizeVirtualScrollStrategy, RxVirtualScrollViewportComponent],
template: `
<rx-virtual-scroll-viewport autosize>
<div class="item" *rxVirtualFor="let item of items$">{{ item.content }}</div>
</rx-virtual-scroll-viewport>
`,
})
export class MyComponent {
items$ = inject(DataService).getItems();
}

Extending with a custom strategy

All parts of the package are based on injection tokens. To provide a custom strategy, create a directive that provides itself as RxVirtualScrollStrategy and extends it (the abstract base already implements some helpers).

import { Directive } from '@angular/core';
import { RxVirtualScrollStrategy } from '@rx-angular/template/virtual-scrolling';

@Directive({
selector: 'rx-virtual-scroll-viewport[custom]',
providers: [{ provide: RxVirtualScrollStrategy, useExisting: CustomScrollStrategy }],
})
export class CustomScrollStrategy extends RxVirtualScrollStrategy {}

See also