2

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>

  • I believe you don't need to use the FileReader. You can upload it directly. Check this: https://stackoverflow.com/questions/46988902/how-to-upload-image-to-firebase-cloud-storage-from-input-field/47788945 – yazantahhan Apr 04 '21 at 20:21
  • Thank you for this. But I guess the problem is the image doesn't generate download url because I check the variable "uploadedImage" and its undefined. – John Paulo A. Geronimo Apr 05 '21 at 00:46
  • But did you check that it was uploaded successfully? You always need an error callback to catch the errors in the `storage.upload` – yazantahhan Apr 05 '21 at 07:56
  • unfortunately, it doesnt upload successfully. But I have a question, is uploading image directly in Firestore is good, this is one of my alternative solution. – John Paulo A. Geronimo Apr 09 '21 at 13:56
  • I don't believe there is an issue with uploading directly. I believe it should work as mentioned in the link I added – yazantahhan Apr 10 '21 at 12:27
  • Sir I will modify what the code say in the link you gave but unfortunately, image doesnt load. ` onFileChosen(event: Event) { const pickedFile = (event.target as HTMLInputElement).files[0]; if (!pickedFile) { return; } this.selectedImage = pickedFile; }` – John Paulo A. Geronimo Apr 11 '21 at 11:07
  • For the additional sir, new error occur but I updated my code above. – John Paulo A. Geronimo Apr 11 '21 at 11:15

1 Answers1

2

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.

zxcsv
  • 71
  • 5