@@ -464,6 +464,97 @@ To enable window scrolling, simply add the `scrollWindow` directive to the `rx-v
464
464
</rx-virtual-scroll-viewport >
465
465
```
466
466
467
+ ## Reverse Infinite Scroll (keepScrolledIndexOnPrepend)
468
+
469
+ :::note Infinite Scrolling != Virtual Scrolling
470
+
471
+ Infinite scrolling is a scrollable pagination. Instead of loading all elements at once, they are
472
+ added to the list when the user hits a certain scroll position. By default you are starting at the top and new data
473
+ is _ appended_ to the list, when the user hits the bottom of the list.
474
+
475
+ :::
476
+
477
+ When implementing a _ reversed_ infinite scroller, you are starting from the _ bottom_ of the list and _ prepend_ data when
478
+ users hit the top of the list. This is usually the case for chat windows like whatsapp.
479
+
480
+ In order to support this behavior, the virtual scroll strategies need to adjust the scrolling behavior. They should keep the
481
+ currently scrolled to index stable when new data is prepended to the list.
482
+
483
+ You can tell the scroll strategies to do so by setting the ` keepScrolledIndexOnPrepend ` flag to ` true ` .
484
+
485
+ See the following example implementation.
486
+
487
+ <Tabs >
488
+
489
+ <TabItem value = " component" label = " Component" >
490
+
491
+ ``` typescript title="reverse-infinite-list.component.ts"
492
+ import { AutoSizeVirtualScrollStrategy , ListRange , RxVirtualFor , RxVirtualScrollViewportComponent } from ' @rx-angular/template/experimental/virtual-scrolling' ;
493
+
494
+ @Component ({
495
+ imports: [RxVirtualScrollViewportComponent , RxVirtualFor , AutoSizeVirtualScrollStrategy ],
496
+ })
497
+ export class ReverseInfiniteListComponent {
498
+ initialScrollIndex = 19 ;
499
+
500
+ private dataService = inject (MessageService );
501
+ // the currently rendered list range
502
+ listRange: ListRange = { start: 0 , end: 0 };
503
+ // attach to scrollIndexChanged
504
+ scrolled$ = new Subject <number >();
505
+ messages$ = this .scrolled$ .pipe (
506
+ // only fetch when hitting the start
507
+ filter (() => this .listRange .start === 0 ),
508
+ // start with the first request
509
+ startWith (0 ),
510
+ // index will be the page we want to fetch
511
+ exhaustMap ((_ , index ) => {
512
+ return this .dataService .getMessages (index );
513
+ }),
514
+ scan (
515
+ (messages , newMessages ) => [
516
+ ... newMessages , // <- append new messages
517
+ ... messages ,
518
+ ],
519
+ [],
520
+ ),
521
+ );
522
+
523
+ trackMessage = (index : number , message : Message ) => {
524
+ return message .id ;
525
+ };
526
+ }
527
+ ```
528
+
529
+ </TabItem >
530
+ <TabItem value = " template" label = " Template" >
531
+
532
+ ``` html title="reverse-infinite-list.component.html"
533
+ <rx-virtual-scroll-viewport #viewport autosize keepScrolledIndexOnPrepend [initialScrollIndex] =" initialScrollIndex" (viewRange) =" listRange = $event" (scrolledIndexChange) =" scrolled$.next($event)" >
534
+ <div
535
+ *rxVirtualFor ="
536
+ let item of messages$;
537
+ trackBy: trackMessage;
538
+ renderCallback: viewsRendered$
539
+ "
540
+ >
541
+ <div >
542
+ <div >{{ item.message.text }}</div >
543
+ <div >{{ item.sendAt | date }}</div >
544
+ </div >
545
+ </div >
546
+ </rx-virtual-scroll-viewport >
547
+ ```
548
+
549
+ </TabItem >
550
+
551
+ </Tabs >
552
+
553
+ This is how the implementation looks like in real life, based on the example
554
+ given in our demos application
555
+
556
+ <ReactPlayer playing controls url = { require (' @site/static/img/template/virtual-scrolling/reverse-infinite-scroll.mp4' ).default } />
557
+
467
558
## Advanced Usage
468
559
469
560
### Use render strategies (` strategy ` )
@@ -639,10 +730,11 @@ All of them provide a twitter-like virtual-scrolling implementation, where views
639
730
using css ` transforms ` .
640
731
They also share two inputs to define the amount of views to actually render on the screen.
641
732
642
- | Input | Type | description |
643
- | --------------------- | -------- | -------------------------------------------------------------------------------- |
644
- | ` runwayItems ` | ` number ` | _ default: ` 10 ` _ The amount of items to render upfront in scroll direction |
645
- | ` runwayItemsOpposite ` | ` number ` | _ default: ` 2 ` _ The amount of items to render upfront in reverse scroll direction |
733
+ | Input | Type | description |
734
+ | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
735
+ | ` runwayItems ` | ` number ` | _ default: ` 10 ` _ The amount of items to render upfront in scroll direction |
736
+ | ` runwayItemsOpposite ` | ` number ` | _ default: ` 2 ` _ The amount of items to render upfront in reverse scroll direction |
737
+ | ` keepScrolledIndexOnPrepend ` | ` boolean ` | _ default: ` false ` _ If this flag is true, the virtual scroll strategy maintains the scrolled item when new data is prepended to the list. This is very useful when implementing a reversed infinite scroller, that prepends data instead of appending it |
646
738
647
739
See the layouting technique in action in the following video. It compares ` @rx-angular/template ` vs. ` @angular/cdk/scrolling `
648
740
0 commit comments