0
votes

so I'm an angular app that allows you to upload some stuff to Cloud Storage. say, a title, a description and some images. I am storing the images in firebase storage and then adding the download URLs to firebase aswell. I'm constructing a method that takes in an array of files, crops the images to a more usable size and then uploads those files to firebase storage:

  uploadImages(images, title) {
    const urls: string[] = [];
    this.ng2ImgToolsService.resize(images, 700, 400).subscribe(result => {
      const uploadImage = new File([result], result.name);

      const path = `${title}/${Date.now()}_${uploadImage.name}`;
      const ref = this.storage.ref(path);
      const task = this.storage.upload(path, uploadImage);

      task.snapshotChanges().pipe(tap(), finalize(async () => {
        const url = await ref.getDownloadURL().toPromise;
        urls.push(url);
      }));  

    }, error => {
      console.log('resize failed, returning original images: ' + error)
    });

    return urls;
  }

this obviously doesn't work. I've also tried wrapping everything in a promise so i could do;

const urls = await this.uploadImages(...);

but at this point I have observables in observables in a promise, and I must admit that I'm in over my head. the times I've worked with async tasks they've been more straight forward.

I spend a couple more days reading up on RxJs but I'm afraid I can't figure it out on my own.

to summarize, this function needs to return an array of urls, I need to be able to await for this array, so basically wait with uploading everything to firestore until I have the download urls.

edit Alright after playing around with dockleryxk answer, I'm still running into some issues. First of all, the "ng2ImgToolsService.resize(images, 700, 400)" function takes in an array of images, and returns a promise that will pas through each cropped image, once it is done, in staid of all of them at once. So if I try to wrap this in a promise and resolve the output of this observable it will only return one image (whichever gets cropped first). So I'd have to wait for the observable to be done emitting data? Is this possible?

Secondly, if I indeed split everything up so that once I have an array of cropped images i'd put those images into a new function. In that function I'd have to loop over that array and upload them one by one.

 uploadImages2(images, title) {
    const observables = [];

    for (let image of images) {
      const path = `${title}/${Date.now()}_${image.name}`;
      const ref = this.storage.ref(path);
      const task = this.storage.upload(path, image);

      task.snapshotChanges().pipe(tap(), finalize(async () => {
        const url =  ref.getDownloadURL();
        observables.push(url);
      }));
    }

    return new Promise((resolve, reject) => {
      Observable.combineLatest(observables).subscribe(
        responses => {
          resolve(responses);
        },
        error => {
          console.log('error:', error);
          reject(error);
        }
      );
    });
  }

this does upload the images correctly but doesn't return the download url array. it basically freezes somewhere in the promise.

2

2 Answers

0
votes

Since you are more comfortable with promises, you could do something like make an array of your observables and then combine them.

const observables = [
  ...
];

return new Promise((resolve, reject) => {
  Observable.combineLatest(observables).subscribe(
    responses => {
      // responses array will be in the same order as requests
      console.log('responses:', responses);
      resolve(responses);
    },
    error => {
      console.log('error:', error);
      reject(error);
    }
  );
});

EDIT: I didn't realize how the package worked exactly, try something like this:

uploadImages(images, title) {
  const urls: string[] = [];
  return new Promise((resolve, reject) => {
    this.ng2ImgToolsService.resize(images, 700, 400).subscribe(result => {
      const uploadImage = new File([result], result.name);

      const path = `${ title }/${ Date.now() }_${ uploadImage.name }`;
      const ref = this.storage.ref(path);
      const task = this.storage.upload(path, uploadImage);

      task.snapshotChanges().pipe(tap(), finalize(async () => {
        const url = await ref.getDownloadURL().toPromise();
        urls.push(url);
        // in this case, all images should be done
        if (urls.length === images.length) {
          resolve(urls);
        }
      }));

    }, error => {
      console.log('resize failed, returning original images: ' + error);
      reject(error);
    });
  });
}
0
votes

Alright, I figured it all out. Huge thanks to dockleryxk for a generally working solution. Splitting up my function and basically doing dockleryxk' tactic twice ended up working:

The function to resize incoming images:

resizeImages(images: File[], width: number, height: number) {

    /*
    this function takes in an array of images, a width and a height.
    if the array contains 'png' or 'jpg' files. it will scale down the images to either the width or the height given.
    if the array contains other image files it will just return them.
    */

    const toResize: File[] = [];
    const resized: File[] = [];

    for (const file of images) {
      if (this.getFileExtension(file) === 'png' || this.getFileExtension(file) === 'jpg') {
        toResize.push(file);
      } else {
        resized.push(file);
      }
    }

    return new Promise((resolve, reject) => {
      if (toResize.length > 0) {
        this.ng2ImgToolsService.resize(toResize, width, height).subscribe(response => {
          resized.push(this.blobToFile(response, response.name));

          if (resized.length === images.length) {
            resolve(resized);
          }
        }, error => {
          console.log('error:', error);
          reject(error);
        });
      } else {
        resolve(resized);
      }
    });
  }

the function to upload those images:

  uploadImages(images: File[], title: string) {
    return new Promise((resolve, reject) => {
      const urls: string[] = [];
      for (const file of images) {
        const path = `${title}/${Date.now()}_${file.name}`;
        const ref = this.storage.ref(path);
        const task = this.storage.upload(path, file);

        task.snapshotChanges().pipe(
          finalize(() => {
            ref.getDownloadURL().subscribe(url => {
              urls.push(url);

              if (images.length === urls.length) {
                resolve(urls);
              }
            });
          })
        ).subscribe();
      }
    });
  }

and then using them in your code:

const imgs: any = await this.resizeImages(data.images, 700, 400);
console.log(imgs);
const urls: any = await this.uploadImages(imgs, data.title);
console.log(urls);

I can tell that it's not the best solution, I'm waiting unnecessarily long and this could be sped up. So if anyone has a better solution I'd love to hear it. but this works!