htmlangulartypescriptangular-signalsangular19

How to use httpResource to fetch an image and render in HTML as a URL


I am experimenting with httpResource, I am trying to fetch an image using the new httpResource method, but I am not sure how to convert the fetched image render in an img tag.

Below is a sample of what I am trying to do, I checked the network tab and I can see the image is being fetched, but it is not showing inside the image tag.

Angular.dev - HttpResource Docs

I think I need to convert this to a URL.

@Component({
  selector: 'app-root',
  imports: [FormsModule, CommonModule],
  template: `
    <input [(ngModel)]="height" type="number"/>
    <input [(ngModel)]="width" type="number"/>
    <hr/>
    @let imageStatus = imageResource.status();
    @if(imageResource.value(); as imageVal) {
      <img [src]="imageVal"/>
    } 
  `,
})
export class App {
  sanitizer = inject(DomSanitizer);
  height = signal<number>(200);
  width = signal<number>(300);
  rs = ResourceStatus;
  imageResource = httpResource(
    () => `https://picsum.photos/${this.height()}/${this.width()}`
  );
}

Stackblitz Demo


Solution

  • If you want to fetch an image, then I think httpResource.blob will be the best option for fetching the image.

    I am choosing blob because it is the minimal type (Read the data, but do not write) to achieve the functionality What is the difference between an ArrayBuffer and a Blob?

    imageResource = httpResource.blob(() => `https://picsum.photos/${this.height()}/${this.width()}`, { ... });
    

    But the HTML element img accepts either a path or a URL to the resource.

    So we need to convert this blob to a url string.

    We can then leverage parse property of the second argument of httpResource which is a part of httpResourceOptions, this can be used to transform the fetched blob into a URL.

    sanitizer = inject(DomSanitizer);
    height = signal<number>(200);
    width = signal<number>(300);
    rs = ResourceStatus;
    imageResource = httpResource.blob(
      () => `https://picsum.photos/${this.height()}/${this.width()}`,
      {
        parse: (blob: Blob) => {
          let image = null;
          if (blob) {
            let objectURL = URL.createObjectURL(blob);
            image = this.sanitizer.bypassSecurityTrustUrl(objectURL);
            return objectURL;
          }
          return image;
        },
      }
    );
    

    We can then use URL: createObjectURL() to convert the blob to a URL string.

    As an additional safety, we can sanitize the url using the DomSanitizer method bypassSecurityTrustUrl and finally return the safe url.


    We can use @switch along with the resource api signals:

    status -> status of the resource fetched

    value -> value of the resource fetched

    Combine this with ResourceStatus, we can show loading status and error status.

    <input [(ngModel)]="height" type="number"/>
    <input [(ngModel)]="width" type="number"/>
    <hr/>
    @let imageStatus = imageResource.status();
    @switch(imageStatus) {
      @case (rs.Resolved) {
        @let image = imageResource.value();
        <img [src]="image"/>
      } 
      @case (rs.Error) {
      Failed to fetch the image...
      } 
      @default {
        Loading...
      } 
    }
    

    Full Code:

    import {
      Component,
      signal,
      computed,
      ResourceStatus,
      inject,
    } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { CommonModule } from '@angular/common';
    import { DomSanitizer } from '@angular/platform-browser';
    import {
      httpResource,
      HttpResourceOptions,
      HttpResourceRequest,
      provideHttpClient,
    } from '@angular/common/http';
    import { bootstrapApplication } from '@angular/platform-browser';
    
    @Component({
      selector: 'app-root',
      imports: [FormsModule, CommonModule],
      template: `
        <input [(ngModel)]="height" type="number"/>
        <input [(ngModel)]="width" type="number"/>
        <hr/>
        @let imageStatus = imageResource.status();
        @switch(imageStatus) {
          @case (rs.Resolved) {
            @let image = imageResource.value();
            <img [src]="image"/>
          } 
          @case (rs.Error) {
          Failed to fetch the image...
          } 
          @default {
            Loading...
          } 
        }
      `,
    })
    export class App {
        sanitizer = inject(DomSanitizer);
        height = signal<number>(200);
        width = signal<number>(300);
        rs = ResourceStatus;
        imageResource = httpResource.blob(
          () => `https://picsum.photos/${this.height()}/${this.width()}`,
          {
            parse: (blob: Blob) => {
              let image = null;
              if (blob) {
                let objectURL = URL.createObjectURL(blob);
                image = this.sanitizer.bypassSecurityTrustUrl(objectURL);
                return objectURL;
              }
              return image;
            },
          }
        );
    }
    
    bootstrapApplication(App, {
      providers: [provideHttpClient()],
    });
    

    Stackblitz Demo