Reputation: 105
I'm building an app with ionic 3 and firebase. Basically, I want to be able to take multiple pictures, upload those to firebase storage, then get the link back that points to those pictures and put those links in a document in firestore. However, I am experiencing weird behavior with the array containing the downloadURLs. If I run the function once, it upload a document to firestore but the array with the download links is empty. If I run the function a second time, the links show up. Here's my code:
Variables:
export class AboutPage {
public photos: any;
public downloadURLs: any;
public link: string;
public base64Image: string;
ngOnInit() {
this.photos = [];
this.downloadURLs = [];
}
Function:
// Note: The uploading of the images works completely fine
uploadForm() {
//var testArray = [];
var kuerzel = "teacher";
var lehrerKuerzel = kuerzel.toUpperCase();
var date = Date();
var length = this.photos.length;
var i = 0;
while(i < length){
const ref = firebase.storage().ref(`pruefungen/${lehrerKuerzel}/${date}/${i}`);
ref.putString(this.photos[i], 'data_url').then((snapshot) => {
this.downloadURLs.push(snapshot.downloadURL);
});
if(i + 1 == length)
{
var db = firebase.firestore().collection(`${lehrerKuerzel}`);
db.add({
TestName: pruefungsName,
Klasse: klasse,
Abteilung: abteilung,
Datum: date,
DownloadURLs: this.downloadURLs,
Length: length,
I: i
});
}
i += 1;
}
}
What I have tried:
Using a local Array
Putting the db.add({}); in the .then(), while the array always showed up fully, the if statement containing i was always true, as for some reason i was always == length. Thus there were always too many items in the collection.
Upvotes: 2
Views: 4256
Reputation: 7067
The upload function res.putString
works asynchronously, which means your while
loop runs to completion before the files are actually uploaded, so this.downloadURLs === []
at the time the record is added, and only gets populated later, as the photos finish one by one.
You should wait for all uploads to complete before adding the record, so you already have all download urls to write on the db.
One way to do that is with Observable ForkJoin, described in this page as:
If you are familiar with Promises this is very similar to Promise.all(). The forkJoin() operator allows us take a list of Observables and execute them in parallel. Once every Observable in the list emits a value the forkJoin with emit a single Observable value containing a list of all the resolved values from the Observables in the list.
That article provides you with various examples of dealing with this kind of requirements.
In your particular case, I would advise you to make the array of storage calls in the loop, and then using ForkJoin to wait for the result and then make the record, something like this:
// extra import you'll need
import { forkJoin } from "rxjs/observable/forkJoin";
var kuerzel = "teacher";
var lehrerKuerzel = kuerzel.toUpperCase();
var date = Date();
var upload = this.photos.map(i => {
const ref = firebase.storage().ref(`pruefungen/${lehrerKuerzel}/${date}/${i}`);
return ref.putString(i, 'data_url');
});
forkJoin(upload).subscribe(results => {
// results is an array of snapshots
var db = firebase.firestore().collection(`${lehrerKuerzel}`);
db.add({
TestName: pruefungsName,
Klasse: klasse,
Abteilung: abteilung,
Datum: date,
DownloadURLs: results.map(i => i.downloadURL),
Length: this.photos.length,
I: this.photos.length - 1
});
});
Haven't tested the code but the idea should work, and again you can find more complete examples in the linked article.
Upvotes: 3
Reputation: 2862
Use an array of promises
with Promise.all
:
...
let promises = [];
while(i < length){
promises.push(new Promise(resolve, reject) {
const ref = firebase.storage().ref(`pruefungen/${lehrerKuerzel}/${date}/${i}`);
ref.putString(this.photos[i], 'data_url').then((snapshot) => {
this.downloadURLs.push(snapshot.downloadURL);
resolve();
}).catch(e => {
console.log(e);
reject();
});
})
i++;
}
Promise.all(promises).then(() => {
var db = firebase.firestore().collection(`${lehrerKuerzel}`);
db.add({
TestName: pruefungsName,
Klasse: klasse,
Abteilung: abteilung,
Datum: date,
DownloadURLs: this.downloadURLs,
Length: length,
I: i
});
})
Upvotes: 1