diff --git a/apps/demos/src/app/features/template/rx-virtual-for/rx-virtual-for.menu.ts b/apps/demos/src/app/features/template/rx-virtual-for/rx-virtual-for.menu.ts
index edb77a425d..33cec9af82 100644
--- a/apps/demos/src/app/features/template/rx-virtual-for/rx-virtual-for.menu.ts
+++ b/apps/demos/src/app/features/template/rx-virtual-for/rx-virtual-for.menu.ts
@@ -23,4 +23,8 @@ export const RX_VIRTUAL_FOR_MENU_ITEMS = [
label: 'Crazy Update',
link: 'crazy-update',
},
+ {
+ label: 'Scroll To',
+ link: 'scroll-to',
+ },
];
diff --git a/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-experiments.module.ts b/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-experiments.module.ts
index c8bbc2bde9..a1619b70ac 100644
--- a/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-experiments.module.ts
+++ b/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-experiments.module.ts
@@ -27,6 +27,7 @@ import { VirtualForMonkeyTestComponent } from './virtual-for-monkey-test.compone
import { VirtualForReverseInfiniteScrollComponent } from './virtual-for-reverse-infinite-scroll.component';
import { VirtualForScrollWindowDemoComponent } from './virtual-for-scroll-window-demo.component';
import { VirtualForCustomScrollableDemoComponent } from './virtual-for-scrollable-demo.component';
+import { VirtualForScrollToDemoComponent } from './virtual-for-scrollto-demo.component';
@NgModule({
imports: [
@@ -60,6 +61,10 @@ import { VirtualForCustomScrollableDemoComponent } from './virtual-for-scrollabl
path: 'crazy-update',
component: VirtualForCrazyUpdateComponent,
},
+ {
+ path: 'scroll-to',
+ component: VirtualForScrollToDemoComponent,
+ },
]),
ValueProvidersModule,
RxLet,
diff --git a/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-scrollto-demo.component.ts b/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-scrollto-demo.component.ts
new file mode 100644
index 0000000000..21bfdea9e3
--- /dev/null
+++ b/apps/demos/src/app/features/template/rx-virtual-for/virtual-rendering/virtual-for-scrollto-demo.component.ts
@@ -0,0 +1,108 @@
+import { coerceNumberProperty } from '@angular/cdk/coercion';
+import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
+import { NgTemplateOutlet } from '@angular/common';
+import {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ OnInit,
+ QueryList,
+ TemplateRef,
+ ViewChild,
+ ViewChildren,
+} from '@angular/core';
+import { RxStrategyNames } from '@rx-angular/cdk/render-strategies';
+import { patch, toDictionary, update } from '@rx-angular/cdk/transformations';
+import { RxState } from '@rx-angular/state';
+import {
+ BehaviorSubject,
+ combineLatest,
+ defer,
+ pairwise,
+ ReplaySubject,
+ Subject,
+ switchMap,
+} from 'rxjs';
+import {
+ distinctUntilChanged,
+ map,
+ shareReplay,
+ startWith,
+ withLatestFrom,
+} from 'rxjs/operators';
+import { ArrayProviderComponent } from '../../../../shared/debug-helper/value-provider/array-provider/array-provider.component';
+import { TestItem } from '../../../../shared/debug-helper/value-provider/index';
+import {
+ AutoSizeVirtualScrollStrategy,
+ RxVirtualFor,
+ RxVirtualScrollViewportComponent,
+} from '@rx-angular/template/experimental/virtual-scrolling';
+
+@Component({
+ selector: 'rxa-virtual-for-scroll-to',
+ template: `
+
Scroll To
+
+ `,
+ styles: [
+ `
+ :host {
+ display: flex;
+ flex-flow: column;
+ height: 100%;
+ }
+ .container {
+ height: 100%;
+ flex-grow: 1;
+ }
+ rx-virtual-scroll-viewport {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ }
+
+ .item {
+ width: 100%;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: lightpink;
+ border-top: 1px solid gray;
+ }
+ `,
+ ],
+ providers: [RxState],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [
+ RxVirtualScrollViewportComponent,
+ AutoSizeVirtualScrollStrategy,
+ RxVirtualFor,
+ ],
+})
+export class VirtualForScrollToDemoComponent implements OnInit {
+ items: string[] | undefined;
+ initialScrollIndex = 5;
+
+ ngOnInit() {
+ this.items = Array.from({ length: 100 }, (_, i) => i.toString());
+ }
+
+ onScrolledIndexChange(index: number) {
+ console.log('onScrolledIndexChange', index);
+ }
+}
diff --git a/libs/template/experimental/virtual-scrolling/src/lib/scroll-strategies/autosize-virtual-scroll-strategy.ts b/libs/template/experimental/virtual-scrolling/src/lib/scroll-strategies/autosize-virtual-scroll-strategy.ts
index 0168d72d2c..2df3b7b2a8 100644
--- a/libs/template/experimental/virtual-scrolling/src/lib/scroll-strategies/autosize-virtual-scroll-strategy.ts
+++ b/libs/template/experimental/virtual-scrolling/src/lib/scroll-strategies/autosize-virtual-scroll-strategy.ts
@@ -14,6 +14,7 @@ import {
merge,
MonoTypeOperatorFunction,
Observable,
+ of,
pairwise,
ReplaySubject,
Subject,
@@ -28,6 +29,7 @@ import {
mergeMap,
startWith,
switchMap,
+ take,
takeUntil,
takeWhile,
tap,
@@ -225,6 +227,14 @@ export class AutoSizeVirtualScrollStrategy<
private readonly _scrolledIndex$ = new ReplaySubject(1);
/** @internal */
readonly scrolledIndex$ = this._scrolledIndex$.pipe(distinctUntilChanged());
+ /**
+ * @internal
+ * The action used to kick off the scroll process
+ */
+ private scrollToTrigger$ = new Subject<{
+ scrollTop: number;
+ behavior?: ScrollBehavior;
+ }>();
/** @internal */
private _scrolledIndex = 0;
/** @internal */
@@ -332,6 +342,7 @@ export class AutoSizeVirtualScrollStrategy<
this.maintainVirtualItems();
this.calcRenderedRange();
this.positionElements();
+ this.listenToScrollTrigger();
}
/** @internal */
@@ -352,7 +363,7 @@ export class AutoSizeVirtualScrollStrategy<
if (_index !== this.scrolledIndex) {
const scrollTop = this.calcInitialPosition(_index);
this._scrollToIndex = _index;
- this.scrollTo(scrollTop, behavior);
+ this.scrollToTrigger$.next({ scrollTop, behavior });
}
}
@@ -796,6 +807,25 @@ export class AutoSizeVirtualScrollStrategy<
.subscribe();
}
+ /** listen to API initiated scroll triggers (e.g. initialScrollIndex) */
+ private listenToScrollTrigger(): void {
+ this.scrollToTrigger$
+ .pipe(
+ switchMap((scrollTo) =>
+ // wait until containerRect at least emitted once
+ this.containerSize === 0
+ ? this.viewport!.containerRect$.pipe(
+ map(() => scrollTo),
+ take(1),
+ )
+ : of(scrollTo),
+ ),
+ this.until$(),
+ )
+ .subscribe(({ scrollTop, behavior }) => {
+ this.scrollTo(scrollTop, behavior);
+ });
+ }
/** @internal */
private adjustContentSize(position: number) {
let newContentSize = position;
diff --git a/libs/template/experimental/virtual-scrolling/src/lib/virtual-scroll-viewport.component.scss b/libs/template/experimental/virtual-scrolling/src/lib/virtual-scroll-viewport.component.scss
index 9359a3e845..d16f86983b 100644
--- a/libs/template/experimental/virtual-scrolling/src/lib/virtual-scroll-viewport.component.scss
+++ b/libs/template/experimental/virtual-scrolling/src/lib/virtual-scroll-viewport.component.scss
@@ -12,6 +12,10 @@
position: absolute;
top: 0;
bottom: 0;
+
+ > * {
+ position: absolute;
+ }
}
&__sentinel {
width: 1px;