Search code examples
angularrxjssubscription

How to use this RxJs subsctriptions correctly?


So I have a question about mechanics of Angular and RxJs. I have server-side pagination on my backend and I have three components on frontend: 1) list with data, 2) some filters, 3) pagination component. How do I subscribe on two streams (pagination and filters) to call getAll method with new params from this components? Perhaps I should use combineLatest operator? Its correct? How can I improve it?

const pagSettingsSub = this.paginatorSrv.subscriber$
      .pipe(
        tap(data => {
          this.pagSettings = data
        })
      )
    const filterSettingsSub = this.filterAndSortSrv.subscriber$
      .pipe(
        tap(data => this.filterAndSortSettings = data)
      )

    combineLatest([pagSettingsSub, filterSettingsSub])
      .pipe(
        tap(() => this.isLoading$.next(true)),
        mergeMap(() => this.messageSrv.getAll(this.pagSettings, this.filterAndSortSettings)),
        takeUntil(this.destroy$)
      )
      .subscribe({
        next: () => this.isLoading$.next(false),
        error: () => this.isLoading$.next(false),
        complete: () => this.isLoading$.next(false)
      })`

Solution

  • Yes, you can use the combineLatest operator to combine the streams from your pagination and filters components, and trigger a new request to your server whenever either of these streams emits a new value.

    But since you mentioned in the comments of this answer that you will also need to reset the pagination on filter change, i would recommend that you approach it like this:

    const resultData = this.filterAndSortSrv.subscriber$.pipe(
        tap(() => this.paginationSrv.resetPage()),
        switchMap((filterSettings) => combineLatest([of(filterSettings), this.paginatorSrv.subscriber$])),
        tap(() => this.isLoading$.next(true)),
        switchMap(([filterSettings, pageSettings]) => this.messageSrv.getAll(pageSettings, filterSettings)),
        takeUntil(this.destroy$),
        catchError(error => {
             // do error handling stuff
        }),
        finalize(() => this.isLoading$.next(false))
    )
    

    Here's what's happening in this code:

    1. We create two observables, one for the pagination settings and one for the filter settings. These observables emit whenever the corresponding settings are changed in the pagination and filters components.
    2. We pipe through the filter observable to always reset the pagination whenever the filter changes.
    3. We use combineLatest to combine these two observables into a single observable that emits whenever either of the component settings change.
    4. We use tap to set the isLoading$ subject to true whenever the combined observable emits.
    5. We use switchMap to make a new request to the server using the latest pagination and filter settings whenever the combined observable emits.
    6. We use takeUntil to ensure that the subscription is cancelled when the component is destroyed.
    7. Use the catchError operator to handle exceptions
    8. We use finalize to set the isLoading$ subject to false whenever the observable completes or errors.

    This code should work well for your use case. However, you can also consider using debounceTime or distinctUntilChanged operators to prevent making unnecessary requests to the server if the user is typing in a filter input or changing the pagination settings frequently. These operators can help reduce the number of requests sent to the server, resulting in a more responsive and efficient user interface.

    You now can use this operator with the async pipe in your html!