Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Angular HttpClient Blob

Using Angular’s HttpClient “blob” response type to display an image.

Demo

Demo of HttpClient blob image

Source Code

You can download the source code and follow along or fork the repository on GitHub:

Install

First, install dependencies:

$ npm install

Next, run the gulp tasks, and then start the Node.js Express server.

$ gulp
$ chmod +x ./dist/bin/www
$ ./dist/bin/www

Then, serve the Angular client using the CLI:

$ npm start

Goals

  1. Create a simple Express Node.js HTTP server.
  2. Mock an image/jpeg REST API response.
  3. Create a simple Angular application to display heros.
  4. Viewing a hero displays the hero’s name and associated image. Again, the image is always the same since I hacked this portion.
  5. Show how to retrieve the JPEG image using the HttpClient with the responseType option set to “blob”.
  6. Show the image.

Server

Let’s briefly look at the Node.js server code. The code is written in TypeScript and compiled to JavaScript, and then executed with Node.js. All of the server code is in the root /server directory.

The image is stored in the /server/public/images directory. For now, I have a single image. And, I’m going to “hack” the response to always return the same image.

Here are some code snippets from the /server/src/api/hero.ts file:

/**
 * @class HerosApi
 */
export class HerosApi {

  /**
   * Create the api.
   * @static
   */
  public static create(router: Router) {
    // code omitted

    router.get("/heros/image/:id([0-9a-f]{24})", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().getImage(req, res, next);
    });
  }

  /**
  * Get a hero image.
  * @param req {Request} The express request object.
  * @param res {Response} The express response object.
  * @param next {NextFunction} The next function to continue.
  */
  public getImage(req: Request, res: Response, next: NextFunction) {
    // verify the id parameter exists
    const PARAM_ID: string = "id";
    if (req.params[PARAM_ID] === undefined) {
      res.sendStatus(404);
      next();
      return;
    }

    // get id
    const id: string = req.params[PARAM_ID];

    // get hero
      Hero.findById(id).then(hero => {

      // verify hero was found
      if (hero === null) {
        res.sendStatus(404);
        next();
        return;
      }

      // always return captain america
      // this is not what you would normally do
      res.sendFile(path.resolve(__dirname,"../../public/images/captain-america.jpg"), next);
    }).catch(next);
  }
}

To quickly review:

Client

The Angular client application is stored within the root /client directory. I have a single heros module that is lazy loaded. Within the heros module we have two routes:

Further, I have a HerosService that is located at /client/src/app/core/services/heros.service.ts that uses the HttpClient to GET our heros, and to GET a hero image. Let’s look at the getImage() method:

public getImage(hero: Hero): Observable<Blob> {
  return this.httpClient
    .get(`${this.URL}/image/${hero._id}`, {
      responseType: "blob"
    });
}

Note the responseType property is set to “blob”. Further, also note that the return type of the method is Observable<Blob>.

Next, let’s take a look at the very simple HTML template for the HeroComponent:

<app-layout>
  <h1>{{ (hero | async)?.name }}</h1>
  <img #heroImage />
</app-layout>

Note the #heroImage template reference variable. Over in our component we will use the ViewChild() decorator to get access to the <img> native element.

Finally, let’s look at the HeroComponent:

import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs/Observable";
import { Store } from "@ngrx/store";
import { State, getHero, getImage } from "../../app.reducers";
import { Hero } from "../../models/hero";
import { LoadHeroAction, LoadHeroImageAction } from "../heros.actions";
import { WindowRefService } from "../../core/services/window.service";

import "rxjs/add/operator/do";
import "rxjs/add/operator/filter";
import "rxjs/add/operator/share";
import "rxjs/add/operator/takeWhile";

@Component({
  templateUrl: './hero.component.html',
  styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnDestroy, OnInit {

  public hero: Observable<Hero>;

  @ViewChild("heroImage") image: ElementRef;

  private _window: Window;

  private alive = true;

  constructor(
    private activatedRoute: ActivatedRoute,
    private store: Store<State>,
    private windowRefService: WindowRefService
  ) { }

  ngOnDestroy() {
    this.alive = false;
  }

  ngOnInit() {
    this._window = this.windowRefService.nativeWindow;

    this.hero = this.activatedRoute.paramMap
    .takeWhile(() => this.alive)
    .do(params => {
      this.store.dispatch(new LoadHeroAction({ id: params.get("id") }))
    })
    .switchMap(() => this.store.select(getHero))
    .share();

    const image = this.hero
    .takeWhile(() => this.alive)
    .filter(hero => !!hero)
    .do(hero => this.store.dispatch(new LoadHeroImageAction({ hero: hero })))
    .switchMap(() => this.store.select(getImage))

    image
    .takeWhile(() => this.alive)
    .filter(image => !!image)
    .subscribe(image => {
      this.image.nativeElement.src = this._window.URL.createObjectURL(image)
    })
  }

}

Let’s break this down:

Also, note that I am using a WindowRefService that is injected into my constructor() function. This is to avoid direct access to the URL property on the browser’s globally available window object.

URL Web API

Warning: this solution for displaying an image blob using the HttpClient uses the URL API, which is generally supported by all modern browsers. Make sure you check caniuse.com browser support for URL to be sure that this solution will work for your application’s users.