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?
If I understand right, you need first to complete all the fileupload operations before saving the document, which has to contain all the id
s 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 id
s 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.