Angular: Re-render asynchronous data in image tag

Viewed 622

I am using Angular to fetch user profile picture from backend(Node.js/Express). Everything is working perfectly except for one thing. The Angular does not re-render the HTML that displays the profile picture incase, the user has updated his picture or if no picture is present and user uploads his first image. As expected, the Angular is rendering the HTML only once and isn't re-rendering again. I don't know how can I wait for asynchronous data in HTML as I am directly targeting an endpoint in HTML instead of TS.

Here's my code:

userProfile.component.html

<div class = "imgClass">
    <img class = "img-thumbnail rounded-circle imgclass" 
     
     src="http://localhost:3000/api/getProfilePhoto?id={{cookieData}}"> 

     //angular is sending request to the above endpoint to fetch the image only once at the time 
       application starts or user logs in. How can I send a request again?

    <div class="middle">
      <div class="text"><button type="button" name="button" (click) = "selectImage()" class = "btn 
       btn-outline-primary">&nbsp;<i class="bi bi-plus"></i>&nbsp; </button></div>
      <input type="file" id="imgUpload" (change) = "handleImageInput($event.target.files)">
    </div>
 </div>

userProfile.component.ts

selectImage()
{
  document.getElementById('imgUpload').click();
}

handleImageInput(files: FileList)
{
  this.imageUpload = files.item(0);
  this.uploadImage();
}

uploadImage()
{
  const formData = new FormData();
  const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));

  formData.append("file", this.imageUpload, this.imageUpload.name);

  this.http.post('http://localhost:3000/api/updateImage', formData, {params, responseType: "text"})
  .subscribe(responseData => {

   this.imageChanged = true;  //I have tried using this as *ngIf in HTML but it is not working either
 }
  ,error => {
   console.log("Image uploading failed" + error.message);
 })
}

Does anybody know how can I send the request to an endpoint in HTML once user changes/uploads his first picture?

2 Answers

You need to trigger the image fetch request for each update/upload requests. Or you could adjust the backend to return the image data from the update/upload requests.

Option 1: manually fetch image for each update/upload requests

Use RxJS switchMap operator to switch to image fetch request after the uploading has completed. It'll not be fetched if the uploading failed.

profileImage: any;

selectImage() {
  document.getElementById('imgUpload').click();
}

handleImageInput(files: FileList) {
  this.imageUpload = files.item(0);
  this.uploadImage();
}

uploadImage() {
  const formData = new FormData();
  const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
  formData.append("file", this.imageUpload, this.imageUpload.name);

  this.http.post('http://localhost:3000/api/updateImage', formData, { 
    params, 
    responseType: "text" 
  }).pipe(
    tap(null, error => console.log("Image uploading failed" + error.message)),
    switchMap(_ => this.http.get(`http://localhost:3000/api/getProfilePhoto?id${this.cookieData}`))
  ).subscribe(
    image => {
      this.profileImage = image;
    }, 
    error => {
      console.log("Image fetching failed" + error.message);
    }
  );
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">

Option 2: Return the image from upload/update request

Adjust the backend to return the image data from the Upload POST request.

profileImage: any;

uploadImage() {
  const formData = new FormData();
  const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
  formData.append("file", this.imageUpload, this.imageUpload.name);

  this.http.post('http://localhost:3000/api/updateImage', formData, { 
    params, 
    responseType: "text" 
  }).subscribe(
    image => {
      this.profileImage = image;
    }, 
    error => {
      console.log("Image uploading failed" + error.message);
    }
  );
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">

As a sidenote, using document.getElementById() in Angular will search the whole DOM, not just the individual component. In relatively complex apps, it might lead to performance issues. Instead try to use an event handler or if it's not possible, use Angular ViewChild with a template reference parameter to get an element from the current component's DOM.

if the webservice resolving the image url returns an Observable, you can make the call from typescript like below

imageData$: Observable<number>;
    getImage(id): Observable<string> {
        this.imageData$=http.get(url?id=<some_id>);
        return this.imageData$
    }

and the adding async pipe on it

 <img class = "img-thumbnail rounded-circle imgclass" [src]="imageData$ | async">

Basically The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.


Related