0

I am following this question to create a query that retrieves one random item from a firebase collection in an Angular 9 app using AngularFire.

The solution works fine and I get an expected outcome except when the query returns 0 results. If that happens I want to repeat the query changing some parameters until I get one item, and only then return an observable which to subscribe to in other services. I am learning how to use Observables and RxJS 6 operators, and I think that the expand operator is what I need. However, I am unable to prevent expand from exiting of the recursive loop until I achieve the desired result.

This is my code:

random-query.service.ts

  fetchDocumentoAleatorio(coleccionPath: string): Observable<any> {
    const IdRandom = this.db.createId();
    return this.consultaAleatorio(coleccionPath, '>=', IdRandom)
      .pipe(expand((document: any) => document === null ? this.consultaAleatorio(coleccionPath, '<=', IdRandom) : EMPTY
        ), // I expect to repeat the query here changing '>=' to '<=' and using the same randomly generated Id
        map((document) => { // The recursive loop never takes place since the map operator triggers even if consultaAleatorio() returns null one single time, sending that result to the subscribers
            return publicacion.payload.doc.data();
          }
        ));
  }



consultaAleatorio(path: string, operador: any, idRandom: string): Observable<any> {
    return this.db
      .collection(path, ref => {
        let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;
        query = query.where('random', operador, idRandom);
        query = query.orderBy('random');
        query = query.limit(1);
        return query;
      }).snapshotChanges()
      .pipe(map((arrayDatos: any) => {
        if (arrayDatos && arrayDatos.length) {
          return arrayDatos[0];
        } else {
          return null; // It indeed reaches this point if the query returns empty results
        }
      }));
  }

If any other service makes use of this code, it does it this way:

subscriber-example-service.ts

 private firebaseSubscriptions: Subscription [] = [];
  publicacionAleatoriaSubject = new Subject<IpublicacionMiniatura>();
  private publicacionAleatoria: IpublicacionMiniatura;

constructor(
    private db: AngularFirestore,
    private randomQueryService: RandomQueryService) {
  }
  fetchPublicacionAleatoria(): void {
    this.firebaseSubscriptions.push(this.randomQueryService.fetchDocumentoAleatorio('publicaciones-meta')
      .pipe(map((publicacion) => {
          return {
           //processes data
              };
            })
      )
      .subscribe((publicacionAleatoria: IpublicacionMiniatura) => {
          this.publicacionAleatoria = publicacionAleatoria;
          this.publicacionAleatoriaSubject.next(this.publicacionAleatoria);
        }
      ));

In sum:

  • The recursive loop never takes place since the map operator triggers even if consultaAleatorio() returns null one single time, sending that result to the subscribers
  • When I subscribe to this Observable in other services, it works smoothly and as expected except for the case described, so I think that the problem lies in my misunderstanding on how handle the expand operator to achieve what I need.

Thank you in advance for your time.

Noorhain
  • 3
  • 1
  • What do you mean with _The recursive loop never takes place since the map operator triggers even if consultaAleatorio() returns null one single time_? If `consultaAleatorio()` returns `null` than this `null` gets passed along the pipe chain. `expand` makes sure that that `null` is not the only value passed down the chain. In your case, `expand` should allow to execute `this.consultaAleatorio(coleccionPath, '<=', IdRandom)` recursively until you get something back from the server. – Picci Apr 10 '20 at 20:13

1 Answers1

0

You can utilize retryWhen like below :

 .pipe(map((arrayDatos: any) => {
        if (arrayDatos && arrayDatos.length) {
          return arrayDatos[0];
        } else {
          throw new Error(); //  this causes it to be catched by retryWhen
        }
      }), retryWhen(errors=>errors.pipe(delay(100))); // retry after 100ms

Stackblitz

Edit: Corrected sample and added stackblitz.

Eldar
  • 9,781
  • 2
  • 10
  • 35
  • Did not work.I logged 'null' along throwing the Error to check results. Whenever the query returned 0 results, it just started logging 'null' every 100ms indefinitely. I had to change the way I used retrywhen by the way, this way: ..., retryWhen(errors => errors.pipe( delay(1000))) – Noorhain Apr 10 '20 at 15:50
  • This updated answer and the stackblitz works well. I was unable of implementing it in my code properly and I had to solve my problem rewriting some pieces of my code, but again I am learning RxJS and I am sure that I am making some mistakes that are my fault. Since there have not been any more answers I will mark this as valid – Noorhain Apr 13 '20 at 16:07