John Paulo A. Geronimo
John Paulo A. Geronimo

Reputation: 153

How to Upload Photo to Firebase Storage and Insert the Photo URL in Firestore?

My main goal in this module is to upload the profile picture of the user in Firebase Storage and store the Photo URL in the Firestore (Cloud Firestore).

Note:

  1. The images I uploaded is in the JPG and PNG format only.
  2. This is Cross Platform Application with Ionic and Angular

I try the following scenario:

  1. I directly push the image in the Firestore without uploading it in Firebase Cloud Storage so the name of the image is converted into "data:image,base64" with long characters in it, I don't know if this scenario is okay in my database or system because it runs perfectly, and the images is retrievable. I hope you can help me in this scenario.

Updated Errors:

New Error

This is the field of the user:

User fields

update-profile.component.ts

  userId: string;
  fname: string;
  studentId: string;


  @ViewChild('filePicker') filePickerRef: ElementRef<HTMLInputElement>;
  @Input() showPreview = true;

  //The Selected Image in the File Explorer
  selectedImage: any;


   constructor(private auth: AuthService,
              private afs: AngularFirestore,
              private loadingCtrl: LoadingController,
              private toaster: ToastController,
              private storage: AngularFireStorage,
              private popOverCtrl: PopoverController) { }

  ngOnInit() {

    this.profileEditSub = this.auth.user$.subscribe(user => {
      this.userId = user.userId;
      this.fname = user.userName;
      this.studentId = user.userSchoolId;
      this.selectedImage = user.userPhoto;
    });

  }

  onSubmit(form: NgForm){

    const user = form.value.fname;
    const studentId = form.value.studentId;

    this.onUpdateUser(user, studentId);

  }//

  async onUpdateUser(name: string, studentNo: string){
    const loading = await this.loadingCtrl.create({
      message: 'Updating...',
      spinner: 'crescent',
      showBackdrop: true
    });

    
    loading.present();
    
    const imageUrl = await this.uploadFile(this.userId, this.selectedImage);

    this.afs.collection('user').doc(this.userId).update({
      'userName': name,
      'userSchoolId': studentNo,
      'userPhoto': imageUrl, // Photo URL from Firebase Storage will be saved in here.
      'editedAt': Date.now()
    })
    .then(() => {
      loading.dismiss();
      this.toast('Update Success', 'success');
      this.closePopOver();
    })
    .catch(error => {
      loading.dismiss();
      this.toast(error.message, 'danger');
    })

  }//

  async uploadFile(id, files):Promise<any> {
    if (files && files.length) {
      try {
        const task = await this.storage.ref('images').child(id).put(files[0]);
        return this.storage.ref(`images/${id}`).getDownloadURL().toPromise();
      } catch (error) {
        console.log(error);
      }
    }
  }

  onPickImage() {
    this.filePickerRef.nativeElement.click();
  }

  onFileChosen(event: Event) {
    const pickedFile = (event.target as HTMLInputElement).files[0];

    if (!pickedFile) {
      return;
    }

    const fr = new FileReader();
    fr.onload = () => {
      const dataUrl = fr.result.toString();
      this.selectedImage = dataUrl;
    
    };
    fr.readAsDataURL(pickedFile);
  }

update-profile.component.html

  <!-- For the Image -->
  <div class="picker">
    <img
      class="img"
      [src]="selectedImage"
      *ngIf="selectedImage && showPreview"
    >
  </div>
  <input type="file" accept=".jpg,.png" *ngIf="usePicker" #filePicker (change)="onFileChosen($event)"/>

  <ion-button class="image-btn" (click)="onPickImage()">
    <ion-icon name="camera" slot="icon-only"></ion-icon>
  </ion-button>

  <!-- Other Textual Information -->
  <form #f="ngForm" (ngSubmit)="onSubmit(f)">
    <ion-list lines="full">
      <ion-item>
        <ion-label position="floating">Full Name:</ion-label>
        <ion-input name="fname" required type="text" [(ngModel)]="fname" #userCtrl="ngModel"></ion-input>
      </ion-item>
      <ion-item>
        <ion-label position="floating">Student No:</ion-label>
        <ion-input name="studentId" required type="number" [(ngModel)]="studentId" #studentIds="ngModel"></ion-input>
      </ion-item>

      <ion-button class="ion-margin" type="submit" expand="block" shape="round" [disabled]="!f.valid">Edit User</ion-button>
    </ion-list>
  </form>

Upvotes: 2

Views: 4864

Answers (1)

zxcsv
zxcsv

Reputation: 71

There are a few things that I noticed within your code.

  1. You intended to use the this.selectedImage variable on the selected file of the <input> tag (from the comment with File Explorer), but in the line below, you are reassigning it to a Storage Reference of an image from firestore.
this.profileEditSub = this.auth.user$.subscribe((user) => {
  ...
  this.selectedImage = user.userPhoto; // this line
});
  1. You don't need the FileReader.readAsDataURL() (unless you'll want to use Reference.putString()). According to the Firebase Storage docs, you can directly upload a File API object, just pass it as an argument in storage.ref().put(File). Example from Firebase docs.

Recommendations

  • Use a separate variable for the src of the <img>, then use another variable for storing the current value of the file picker (<input type="file" />).

  • Use the onFileChosen() callback only for assigning the variable for the selected files in File Explorer.

update-profile.component.ts

currentImageUrl: string; // to store the downloadUrl of image to be displayed
selectedFiles: Array<File>; // to store selected files in the File Explorer

this.profileEditSub = this.auth.user$.subscribe(async (user) => {
  ...

  /** 
   * you only store the storage reference (user.userPhoto) in firestore, not the download url,
   * so you need to fetch it again
   */
  this.currentImageUrl = await this.storage
    .ref(user.userPhoto)
    .getDownloadURL()
    .toPromise();
});

onFileChosen(event: any) {
    this.selectedFiles = event.target.files; // just assigns the selected file/s in <input> this.selectedFiles
}

async onSubmit() {
    if (this.selectedFiles.length) {
        // Get selected file
        const file = this.selectedFiles[0];

        // Get the fullPath in Storage after upload
        const fullPathInStorage = await this.uploadImage(this.userId, file);

        /**
         * You can now store the fullPathInStorage in firestore
         *
         * afs.update(...)
         */

        // Get the downloadUrl for the src of img
        this.currentImageUrl = await this.storage
            .ref(fullPathInStorage)
            .getDownloadURL()
            .toPromise();
    }
}

async uploadImage(uid, file): Promise<string> {
    /**
     * You can add random number in file.name to avoid overwrites,
     * or replace the file.name to a static string if you intend to overwrite
     */
    const fileRef = this.storage.ref(uid).child(file.name);

    // Upload file in reference
    if (!!file) {
      const result = await fileRef.put(file);

      return result.ref.fullPath;
    }
}

update-profile.component.html

<input type="file" (change)="onFileChosen($event)"/>

<img [src]="currentImageUrl" />

Also, the ERR_NAME_NOT_RESOLVED might just be due to unstable network.

Upvotes: 2

Related Questions