KamS
KamS

Reputation: 182

Cloud Firestore - How to paginate data with RXJS

I would like to get data from the Cloud Firestore collection (limited to 10), and then after clicking the "load more" button, I would like to get another piece of collection and concat with the previous result.

My code is (base on https://firebase.google.com/docs/firestore/query-data/query-cursors):

import { Component, OnDestroy, OnInit } from '@angular/core';
import { User } from '../../../shared/models/user';
import { UsersService } from '../../../shared/services/users.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.scss']
})
export class UsersComponent implements OnInit, OnDestroy {

  users: User[];
  lastInResponse: any;
  subscription:Subscription;

  constructor(private usersService: UsersService) { }

  ngOnInit(): void {
    this.subscription = this.usersService.getUsers()
    .subscribe(
      res => {
        this.lastInResponse = res[res.length - 1]; // the last object in response
        this.users = res;
      }
    )
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

  loadMore() {
    this.subscription = this.usersService.loadMoreData('users', this.lastInResponse)
    .subscribe(
      res => {
        this.lastInResponse = res[res.length - 1];
        this.users = this.users.concat(res); //concat new values with prevoius
      }
    );
    
  }

}

It works fine, but I would like to clean my code and subscribe to users collection with a more reactive style, like this:

import { Observable } from 'rxjs';
.
.
.
.

users$: Observable<User[]>;
.
.
.
.

ngOnInit(): void {
   this.users$ = this.usersService.getUsers();
}

and then use the "async" pipe in the HTML template.

My question is how could I refactor my code? Especially how to extract the last object in response and then pass it to loadMore() method and finally concat another value with previous.

Thank You.

Upvotes: 0

Views: 228

Answers (1)

user2216584
user2216584

Reputation: 5602

You can try the following code -

import { Component, OnDestroy, OnInit } from '@angular/core';
import { User } from '../../../shared/models/user';
import { UsersService } from '../../../shared/services/users.service';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import {filter, switchMap, tap} from 'rxjs/operators';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.scss']
})
export class UsersComponent implements OnInit, OnDestroy {

  // lets setup a behavior subject to hold the users to be rendered; I have initialized the list with empty array; you can initialized to null as per your need
  usersToRender$ = new BehaviorSubject<User[]>([]);
  
  // this will be used to ensure that the initial users list will be fetched
  initialUsers$: Observable<User[]>;
  
  // this will be used to notify that the user has requested to loadMore data
  loadMore$ = new BehaviorSubject<boolean>(false);

  fetchMore$: Observable<any>;
  
  lastInResponse: any;

  constructor(private usersService: UsersService) { }

  ngOnInit(): void {
  
    this.initialUsers$ = this.usersService.getUsers()
                                         .pipe(
                                                tap(res => {
                                                    this.lastInResponse = res[res.length - 1];
                                                    this.usersToRender$.next(res);
                                                }),
                                              );
    
    this.setupLoadMoreObservable();
  }

  private setupLoadMoreObservable() {
    
    this.fetchMore$ = this.loadMore$.pipe(
                        filter(l => l === true),
                        switchMap(() => {
                            return this.usersService.loadMoreData('users', this.lastInResponse)
                        }),
                        tap(res => {
                            this.lastInResponse = res[res.length - 1];
                            this.usersToRender$.next([...this.usersToRender$.getValue(), ...res]);
                            this.loadMore$.next(false);
                        })
                      )
    
  }
  
  ngOnDestroy() {
    
  }

  loadMore() {
    this.loadMore$.next(true);
  }

}

In the template add the following code. Also, use usersToRender$ to render the users

<ng-container *ngIf="initialUsers$ | async"></ng-container>
<ng-container *ngIf="fetchMore$ | async"></ng-container>

Upvotes: 1

Related Questions