I have a saveForm() function which is expected to perform below operations in order:
- Take form data and add it to FireStore collection as a document.
- On success, loop through(attachmentsFormArray) all the files the user has selected and upload each file to FireStorage.
- When all files are uploaded completely, assign the documentUrl of each file to the corresponding file map on the FireStore document we saved in step #1. Then make api call to actually save the updated firestore document.
Below is my saveForm() function:
saveForm() {
let fixedDepositEntity = this.getEntityFromForm();
this.fixedDepositsFirestoreCollection.add(fixedDepositEntity).then(documentRef => {
if (this.attachmentsFormArray.controls.length !== 0) {
this.attachmentsFormArray.controls.forEach(group => {
let fileRef = this.fireStorage.ref(this.fixedDepositsStorageFolderPath + group.get('fileName').value);
let uploadTask = fileRef.put(group.get('file').value);
// observe percentage changes
uploadTask.percentageChanges().subscribe(percent => {
group.get('percentComplete').setValue(Math.round(percent));
});
// get notified when the download URL is available
uploadTask.snapshotChanges().pipe(
finalize(() => {
fileRef.getDownloadURL().subscribe(url => {
group.get('downloadUrl').setValue(url);
});
}))
.subscribe();
});
}
});
}
Currently, the above code simply loops through the attachmentsFormArray and once the file is uploaded, finally it assigns the downloadUrl to the attachmentsFormArray.
When the user selects the multiple file, I have the below handleFileInput() event handler:
handleFileInput(files: FileList) {
if (!files || files.length === 0) {
return;
}
Array.from(files).forEach(file => {
this.attachmentsFormArray.push(this.formBuilder.group({
fileName: [file.name],
fileSize: [file.size],
label: [''],
file: [file],
downloadUrl: [''],
percentComplete: [''],
uploadTaskState: ['']
}));
});
The AngularFire library provides a snapshotChanges() method which returns Observable<UploadTaskSnapshot>. I would want to combine/merge all these Observables (so that know once all files are completely uploaded) and then subscribe the resultant Observable. But I am not sure how to associate the individual observable result with corresponding file object that the user selected (as described in #3).
I know we can achieve this behavior with RxJs operators, but not sure which one to use in my scenario. Any help is appreciated in advance.
EDIT 1: Implemented as per "Mrk Sef's" answer. It works fine most of the times. However, once in a while the downloadUrl is not set. I'm unable to understand the reason for this intermittent issue.
saveForm() {
try {
this.fixedDepositsFormGroup.disable();
let fixedDepositEntity = this.getEntityFromForm();
this.fixedDepositsFirestoreCollection
.add(fixedDepositEntity)
.then(documentRef => {
this.isBusy = true;
// Changes will be mapped to an array of Observable, once this mapping
// is complete, we can subscribe and wait for them to finish
console.log('Voila! Form Submitted.');
if (this.attachmentsFormArray.controls.length !== 0) {
const changes = this.attachmentsFormArray.controls.map(
group => {
const fileRef = this.fireStorage.ref(this.fixedDepositsStorageFolderPath + group.get('fileName').value);
const uploadTask = fileRef.put(group.get('file').value);
const percentageChanges$ = uploadTask.percentageChanges().pipe(
tap(percent => group.get('percentComplete').setValue(Math.round(percent)))
);
const snapshotChanges$ = uploadTask.snapshotChanges().pipe(
finalize(() => fileRef.getDownloadURL().subscribe(url => group.get('downloadUrl').setValue(url)))
);
return [percentageChanges$, snapshotChanges$];
}
).reduce((acc, val) => acc.concat(val), []);; // Turn our array of tuples into an array
// forkJoin doesn't emit until all source Observables complete
forkJoin(changes).subscribe(_ => {
// By now all files have been uploaded to FireStorage
// Now we update the attachments property in our fixed-deposit document
const attachmentValues = (this.getControlValue('attachments') as any[])
.map(item => <Attachment>{
fileName: item.fileName,
fileSize: item.fileSize,
label: item.label,
downloadUrl: item.downloadUrl
});
documentRef.update({ attachments: attachmentValues });
console.log("Files Uploaded Successfully and Document Updated !");
});
}
})
.finally(() => {
this.fixedDepositsFormGroup.enable();
this.isBusy = false;
});
} finally {
}
}