angularrxjsobservablesubscribeswitchmap

How do I replace subscrib in a subscribe in Angular RxJS?


I have a question about RxJS. I am creating a web app to manage the members of an association. I want to create a button to "reset" the database of a site. The steps are as follows:

Here's the code I've made, which works, but I know there are a few things wrong. I'm new to RxJS, so I don't quite understand all the principles...

newYear(){
    this.amicalisteService.getAmicalistesValides("").subscribe({
      // Envoyer un mail à l'ensemble des amicalistes valides
      next: amicalistes => {
        for(const amicaliste of amicalistes) {
          const to = amicaliste.email;
          const cc = null;
          const subject = "Adhère à l'AEIR";
          const body = ""
          this.amicalisteService.sendMail(null, to, cc, subject, body).subscribe({
            next: response => {
              console.log('E-mail envoyé avec succès !', response);
            },
            error: error => {
              console.error('Erreur lors de l\'envoi de l\'e-mail :', error);
            }
          });
        }
      },
      complete: () => {
        // Supprimer amicalistes, photos et cartes amicalistes
        this.amicalisteService.getAmicalistes().subscribe(amicalistes  => {
          for(const amicaliste of amicalistes){
            this.amicalisteService.deleteAmicalisteById(amicaliste.id).subscribe();
          }
        });
        this.imageService.getImages("amicaliste").subscribe(images => {
          for(const image of images){
            this.imageService.deleteImageByName("amicaliste", this.imageService.getImageName(image.toString())).subscribe();
          }
        });
        this.imageService.getImages("pdf").subscribe(pdfs => {
          for(const pdf of pdfs){
            this.imageService.deleteImageByName("pdf", this.imageService.getImageName(pdf.toString())).subscribe();
          }
        })
      }
      //Refresh...
    })
  }

I've heard it's not good practice to use subscribe() inside subscribe(), but I can't figure out how to do it differently. There are several things I'd like to keep in this code, however. In the complete, the 3 subscribe() run in parallel, if I'm not mistaken. I'd like to keep that. Otherwise, I understand that using a switchMap could help me, but I can't seem to implement it. Can anyone give me some advice?

Thanks you very much !


Solution

  • Here I basically turn a lot of your observables into one, relying mostly on flatMap (concatMap when it comes to rxjs) and forkJoin to combine multiple observables into one which is analogous to Promise.all for promises.

    There's no "monadic" flat map (that I could find) sadly, so we have to map arrays of values to arrays of observable and forkJoin them.

    this.amicalisteService.getAmicalistesValides("").pipe(
      concatMap(amicalistes => { // Observable<Amicaliste[]> -> Observable<void[]>
        return forkJoin( // Observable<void>[] -> Observable<void[]>
          amicalistes.map(amicaliste => { // Amicaliste -> Observable<void>
            const to = amicaliste.email;
              const cc = null;
              const subject = "Adhère à l'AEIR";
              const body = ""
              return this.amicalisteService.sendMail(null, to, cc, subject, body)
                .pipe(tap({
                  next: response => {
                    console.log('E-mail envoyé avec succès !', response);
                  },
                  error: error => {
                    console.error('Erreur lors de l\'envoi de l\'e-mail :', error);
                  }
                }));
          })
        );
      }),
    
      // Then, when the above is completed, execute the rest
      concatMap(() => forkJoin([
        this.amicalisteService.getAmicalistes()
          .pipe(
            concatMap(amicalistes => {
              return forkJoin(amicalistes.map(amicaliste => this.amicalisteService.deleteAmicalisteById(amicaliste.id)));
            })
          ),
        this.imageService.getImages("amicaliste")
          .pipe(
            concatMap(images => {
              return forkJoin(
                images.map(image => this.imageService.deleteImageByName("amicaliste", this.imageService.getImageName(image.toString())))
              )
            })
          ),
        this.imageService.getImages("pdf")
          .pipe(
            concatMap(pdfs => {
              return forkJoin(
                pdfs.map(pdf => this.imageService.deleteImageByName("pdf", this.imageService.getImageName(pdf.toString())))
              )
            })
          )
      ])), 
    );