Skip to content

Commit ffd8fac

Browse files
committed
Add some more explanatory comments to infinite scroll example
1 parent c9c94ad commit ffd8fac

File tree

1 file changed

+52
-16
lines changed

1 file changed

+52
-16
lines changed

apps/examples/src/app/infinite-scroll-example/infinite-scroller/infinite-scroller.component.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import {
1313
Observable,
1414
Subject,
1515
merge,
16-
skip,
16+
first,
1717
} from 'rxjs';
1818
import {
1919
HttpRequestState,
2020
httpRequestStates,
2121
isLoadedState,
22-
loadingState,
2322
} from 'ngx-http-request-state';
2423
import { BookApiResponse } from './model/book';
2524
import { BookService } from './book-service/book.service';
@@ -37,57 +36,94 @@ const PAGE_SIZE = 5;
3736
})
3837
export class InfiniteScrollerComponent {
3938
private readonly bookService = inject(BookService);
39+
40+
/**
41+
* A subject that emits when the user clicks the 'retry' button
42+
* in the error message shown when the last request failed.
43+
*/
4044
private readonly retry$ = new Subject<void>();
45+
/**
46+
* A subject that emits true when the loading spinner at the end of the list is
47+
* scrolled into the viewport, and false when it is scrolled out of the viewport.
48+
*/
4149
private readonly spinnerVisible$ = new Subject<boolean>();
4250

43-
private readonly autoLoadMore$ = defer(() =>
51+
/**
52+
* The trigger to automatically load the next page of results.
53+
*
54+
* Emits when:
55+
*
56+
* - There is no inflight request
57+
* - The previous request was successful
58+
* - The page has been scrolled so far that the loading spinner at the end of the list is visible in the viewport
59+
*
60+
*/
61+
private readonly autoLoadMore$: Observable<void> = defer(() =>
4462
combineLatest([
4563
this.spinnerVisible$,
46-
this.state$.pipe(map(isLoadedState), distinctUntilChanged()),
64+
this.state$.pipe(
65+
map(isLoadedState),
66+
distinctUntilChanged(),
67+
// Delay with debounceTime to allow time for the new value to render and possibly push
68+
// the spinner out of the viewport before we emit again. Prevents an immediate second
69+
// request until the spinner is back in the viewport after scrolling again.
70+
debounceTime(100)
71+
),
4772
]).pipe(
48-
debounceTime(100),
4973
filter(([spinnerVisible, isLoaded]) => spinnerVisible && isLoaded),
5074
map(() => undefined as void)
5175
)
5276
);
5377

78+
/**
79+
* An Observable that emits when we've loaded all items (no more pages of results are available)
80+
*/
5481
private readonly noMoreBooks$ = defer(() =>
5582
this.state$.pipe(
56-
skip(1),
5783
filter(isLoadedState),
5884
map(({ value }) => value.numFound <= value.docs.length),
59-
filter((allDone) => allDone)
85+
filter((allDone) => allDone),
86+
first()
6087
)
6188
);
6289

90+
/**
91+
* The data loading state.
92+
*
93+
* The value property of every state emitted (i.e., each of the Loaded, Loading and Error states) always contains all
94+
* the data loaded so far. So every LoadedState contains all the previous pages loaded, including the most recent one.
95+
*/
6396
readonly state$: Observable<HttpRequestState<BookApiResponse>> = merge(
6497
this.retry$,
6598
this.autoLoadMore$
6699
).pipe(
67100
takeUntil(this.noMoreBooks$),
68101
startWith(undefined as void),
102+
// Use switchScan so we can cumulatively build up the data in the value property of each state emitted:
69103
switchScan(
70-
(prevState: HttpRequestState<BookApiResponse>) =>
104+
(prevState: HttpRequestState<BookApiResponse> | undefined) =>
71105
this.bookService
72106
.findBooks(
73107
'Jasper Fforde',
74-
prevState.value?.docs.length ?? 0,
108+
prevState?.value?.docs.length ?? 0,
75109
PAGE_SIZE
76110
)
77111
.pipe(
78112
httpRequestStates(),
79-
// For a loading or error state, add the previously loaded
80-
// data value to it so it doesn't disappear from the view.
81-
//
82-
// For a loaded state, append the new value to the end of the
83-
// previous value, giving us an ever-growing list of items.
84113
map((state) => ({
85114
...state,
86-
value: mergeData(prevState.value, state.value),
115+
// For a loading or error state, add the previously loaded
116+
// data value to it, so it doesn't disappear from the view.
117+
//
118+
// For a loaded state, append the new value to the end of the
119+
// previous value, giving us an ever-growing list of items.
120+
value: mergeData(prevState?.value, state.value),
87121
}))
88122
),
89-
loadingState<BookApiResponse>()
123+
undefined
90124
),
125+
// shareReplay to prevent an infinite recursion of subscriptions
126+
// (as this observable depends on observables that depend on this)
91127
shareReplay({
92128
bufferSize: 1,
93129
refCount: true,

0 commit comments

Comments
 (0)