0
votes

I have the ability to upload files and successfully to my firebase storage. However, when I bind the src to the download URL I get this error: null:1 GET http://localhost:4200/null 404 (Not Found)

I would like to have a placeholder image to get rid of this error. I can't seem to assign my string to the type of Observable. What would you suggest to do instead?

file-upload.component.ts

import { Component, OnInit } from '@angular/core'; import { AngularFireStorage, AngularFireUploadTask } from 'angularfire2/storage'; import { Observable, Subject } from '../../../node_modules/rxjs'; import { AngularFirestore } from '../../../node_modules/angularfire2/firestore'; import { finalize } from 'rxjs/operators';

@Component({
  selector: 'file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.css']
})

export class FileUploadComponent {
  task: AngularFireUploadTask;
  percentage: Observable<number>;
  snapshot: Observable<any>;
  downloadURL: Observable<string | null> ;





  constructor(private storage: AngularFireStorage, private db: AngularFirestore) {
    this.downloadURL = 'http://via.placeholder.com/350x150';
   }

  fileUpload(event){

    const file = event.target.files[0];

    const filePath = `test/${new Date().getTime()}_${file.name}`;

    const fileRef = this.storage.ref(filePath);

    const task = this.storage.upload(filePath, file);

    //observe percentage changes
    this.percentage = task.percentageChanges();
    //get notified when the download URL is available
    task.snapshotChanges().pipe(
      finalize(
        () => {
          this.downloadURL = fileRef.getDownloadURL();
        }
      )
    )
    .subscribe();


  }


}

file-upload.component.html

<div class="container">
  <div class="card">
    <div class="card-header">
      Firebase Cloud Storage & Angular 5
    </div>
    <div class="card-body">
      <h5 class="card-title">Select a file for upload:</h5>
      <input type="file" (change)="fileUpload($event)" accept=".png,.jpg" />
    </div>
    <div class="progress">
      <div class="progress-bar progress-bar-striped bg-success" role="progressbar" [style.width]="(percentage | async) + '%'" [attr.aria-valuenow]="(percentage | async)" aria-valuemin="0" aria-valuemax="100"></div>
  </div>
  <img *ngIf="downloadURL != null" [src]="downloadURL | async" />
  </div>
</div> 
2

2 Answers

4
votes

You can use BehaviorSubject, it will be much easier than create observable from strings each time.

downloadURL: BehaviorSubject<string>;

In you construction:

construction () {
 this.downloadURL = new BehaviorSubject<string>('http://via.placeholder.com/350x150');
}

When you ready to fetch the real image:

task.snapshotChanges().pipe(
  finalize(
    () => {
      const dlImage = fileRef.getDownloadURL();
      this.downloadURL.next(dlImage);
    }
  )
)
.subscribe();

and in you HTML:

<img src="{{downloadURL | async}}" />

EDIT--
I didn't know that they have changed the upload function in the last update. so we can edit it and use the "dumb" but sure way. Ignore the first part of the answer, and just edit your code.
In HTML:

<img src="{{(downloadURL | async) || placeHolder}}" />

and just add the new placeholder:

placeHolder:string = 'http://via.placeholder.com/350x150';

So, it will wait asynchronously to downloadURL which is the Observable from the storage, till then, it will just place a custom placeHolder that we have provided.

0
votes

I have my image uploader within a form, that is bound to an ngModel productModel, which allows me to use an ngIf

  <img
    *ngIf="productModel.product_image"
    class="card-img-top"
    src="{{ oshopUrl }}uploads/{{ this.whichProdImg() }}"
    alt="Card image cap"
  />

When I do upload an image my function whichProdImg has a conditional, because I use the same form for updates, which means there could be an image filename available:

public returnedWhichProdImg = "";
whichProdImg() {
if (this.urlArr[2]) {
    this.returnedWhichProdImg = this.urlArr[2];
  } else {
    this.returnedWhichProdImg = this.productModel.product_image;
  }
  return this.returnedWhichProdImg;
}

In my onSubmit function that is called when the end user clicks the "upload" button, I only want the name of the image, rather than the whole path, so I use split to be able to use the last element of the array:

this.urlArr = this.productModel.fileToUpload.split("\\");

Hope that helps