Starting a http post after all files are uploaded with a mergeMap

637 Views Asked by At

I am trying to make POST request after all files are uploaded. In my web form, the user can add many files to upload. I have two APIs that handle my request.

One API for my fileupload which saves the file in the database and returns an id (document id), and the second API saves the dossier. Before I call the savedossier API, I have to add all documents ids to the dossier object.

I upload the files with mergeMap:

const fileupload$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
);

fileupload$.subscribe(
  (data) => this.dossier.attachments.push(data),
  (err) => console.error('err: ' + err)
);

const savedossier$ = this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);

savedossier$.subscribe(
  (data) => console.log('Dossier saved!'),
  (err) => console.error('err: ' + err)
);

My problem is the second call (to savedossier$) does not wait until all files are uploaded (because it's async). So the document id's are not added to my dossier object.

I had tested with a "ugly" solution like this:

const fileupload$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
);

fileupload$.subscribe(
  (data) => onuploaded(data),
  (err) => console.error('err: ' + err)
);

onuploaded(data) {
  this.dossier.attachments.push(data)
  if (this.dossier.attachments.length === this.attachments.length) {
    savedossier$.subscribe(
      (data) => console.log('Dossier saved!'),
      (err) => console.error('err: ' + err)
    );
  }
}

const savedossier$ = this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);

This solution with an if statement that compares the array lengths works. But I think it is not a recommended way.

Is there a better solution from rxjs?

2

There are 2 best solutions below

0
On BEST ANSWER

If I understand right, you need first to complete all the fileupload operations before saving the document, which has to contain all the ids returned by the fileupload operations.

I assume also that this.attachments is some sort of array containing the names of the attachments.

If all this is true, I would proceed like this.

First I would create an array of Observables, each representing the http call you want to make to save an attachment and receive its id as response

const saveAttachments = this.attachments).map(
  file => this.http.post<Attachement>(`${this.addAttUrl}`, file),
);

You can then execute all these observables in parallel using forkJoin like this

forkJoin(saveAttachments)

which returns an Observable which emits when all the observables passed as parameter in the saveAttachments array have completed. The value emitted is an array with all the ids returned.

Now you can execute the last operation, i.e. save the dossier, concatenating the savedossier$ observable to the one returned by the above forkJoin. The final code would look like this

const saveAttachments = this.attachments).map(
  file => this.http.post<Attachement>(`${this.addAttUrl}`, file),
);
forkJoin(saveAttachment).pipe(
   concatMap(ids => {
      this.dossier.attachments.push(data);
      return this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier);
   })
)

Note that you have to use the concatMap operator to specify that you want to execute the observable returned by the function passed as parameter to concatMap after the previous observable, i.e. that returned by forkJoin, has completed.

If you want to see some patterns on how to use Observables to compose http calls in different use cases, this article may be of interest.

0
On

You can use switchMap and tap operators:

const fileAndSaveDossier$ = from(this.attachments).pipe(
  mergeMap(file => this.http.post<Attachement>(`${this.addAttUrl}`, file)),
  tap(data => this.dossier.attachments.push(data)),
  switchMap(() => this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier)),
);

fileAndSaveDossier$.subscribe(
  (data) => console.log('Dossier saved!'),
  (err) => console.error('err: ' + err)
);

A nice video explaining switchMap(): https://www.youtube.com/watch?v=6lKoLwGlglE

I am also not sure why are you using mergeMap(). You can just do it as the following:

const fileAndSaveDossier$ = this.http.post<Attachement>(
  `${this.addAttUrl}`, this.attachments
).pipe(
  tap(data => this.dossier.attachments.push(data)),
  switchMap(() => this.http.post<Dossier>(`${this.adddossierUrl}`, this.dossier)),
);