Search code examples
angulartypescriptfirebase-storageangularfire2rxjs5

Insert / Update user profile picture using Firebase Storage and Firebase


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 duplicate the update code. Is there a more convenient way to achieve this? Maybe, if I 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); 

Solution

  • 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);