0
votes

When the user update your profile picture I need to upload his photo to Firebase Storage and immediately retrieve the download URL to update his profile collection.

In another scenario, when the user update his profile, he do not change his profile picture, so I don't have the imageData and also don't need to upload his picture, just update his profile information.

My saveProfile() method:

if (this.profileForm.valid) {
....

  //Check if the user change his picture. If yes, the userProfilePicture has data.
  If (userProfilePicture) {
    //upload the picture to firestorage
      const path = `/profile/${this.profile.id}.jpg`;
      const task =  this.afStorage
        .ref(path)
        .putString(imageData, 'base64', { contentType: 'image/jpeg' });

   // subscribe to download url
      task.downloadURL().subscribe(
        url => {
          //updating profileUrl in the form
          this.profileForm.patchValue({ avatar: url });
      //Doing the update in Firestore
      this.afs.doc(`/profile/${id}`).update(this.profileForm.value);
      });

  }
  Else {
     // just update the profile information
     this.afs.doc(`/profile/${id}`).update(this.profileForm.value);
  }

}

I want to avoid to duplicate the update code in 2 places. Is there a more convenient way tho achieve this? Maybe if there is a way to do the update just when I have the downloadUrl available (in 2 scenarios) something like this:

If (userProfilePicture) {
  //upload the picture
  //subscribe to get the download url
}

//AWAIT for the subscribe to return the download URL and when the download URL is available then update
?? How to do this

//update the profile information with the new or existent download URL
await??? this.afs.doc(`/profile/${id}`).update(this.profileForm.value); 
1
I think downloadURL is depreciated, you might need to switch to getDownloadURL on the .ref(path). await needs a promise. I think that putString returns an upload task that if you call on or then you can get a promise back. getDownloadURL also returns a promise. So perhaps inside the if(profilePicture) block you can await on the upload task and then await on the getDownLoadURL and then instead of using the Else block just always call update(this.profileForm.value) - James Poag

1 Answers

1
votes

FYI downloadURL() on task is depreciated in 5.0, you'll need to use the one on ref after the upload is completed; so you'll need to keep a reference to that around:

const path = `/profile/${this.profile.id}.jpg`;
const ref = this.afStorage.ref(path);
const task =  ref.putString(imageData, 'base64', { contentType: 'image/jpeg' });

task.snapshotChanges().pipe(
  filter(snap => snap.state === storage.TaskState.SUCCESS)
  switchMap(() => ref.getDownloadURL())
).subscribe(url => {
  ...
})

As for reducing the duplicate code, just using update is fine; as that will only update the fields specified. Just leave the profile picture out of the profileForm.

// if there's a profile picture to upload, do so
if (userProfilePicture) {
  const path = `/profile/${this.profile.id}.jpg`;
  const ref = this.afStorage.ref(path);
  const task =  ref.putString(imageData, 'base64', { contentType: 'image/jpeg' });

  task.snapshotChanges().pipe(
    filter(snap => snap.state === storage.TaskState.SUCCESS)
    switchMap(() => ref.getDownloadURL())
  ).subscribe(profilePicture => {
    this.afs.doc(`/profile/${id}`).update({profilePicture});
  })
}

// also just update the rest of the profile, update is non-destructive and only overwrites the fields specified
this.afs.doc(`/profile/${id}`).update(this.profileForm.value);