Michael Keller
Michael Keller

Reputation: 105

Array pushing in ionic 3 not working

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:

Upvotes: 2

Views: 4256

Answers (2)

Jaime Gomez
Jaime Gomez

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

ewizard
ewizard

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

Related Questions