There are many use cases requiring to generate barcodes and QR codes in Angular applications on the fly.

In the Angular ecosystem, there are several libraries that can help developers generate barcodes and QR codes. They are frequently based on popular libraries like ZXing. or JsBarcode.

One of the most promising libraries for generating barcodes and QR codes in Angular is zxing-wasm. It provides a general approach to generate barcodes and QR codes in Angular applications using WebAssembly (WASM). This version of the article uses zxing-wasm version 2.1.0.

zxing-wasm is a C++ implementation of the ZXing barcode library compiled to WebAssembly (WASM). It is fast and efficient and can be used in both - the browser and Node.js.

In this article, we will show how to use zxing-wasm to render barcodes and QR codes in an Angular demo application.

We are using Angular v19 in this article to benefit from the elegance of the new resource API. However, the code should work with some adaptions also with older Angular versions as well.

BarcodeService: Generating the barcodes and QR codes

BarcodeService is a service that generates barcodes and QR codes using. At the end it uses the writeBarcode-method of zxing-wasm/writer.

This service provides a number of methods to generate barcodes and QR codes in different formats and representations:

  • getBarcodeSvg generates a barcode in SVG format and returns it as a string.
  • getBarcodeSvgAsBlob generates a barcode in SVG format and returns it as a Blob.
  • getBarcodeSvgAsSafeUrl generates a barcode in SVG format and returns it as a SafeUrl.
  • getBarcodeBlob generates a barcode image as a Blob.
  • getBarcodeDataUrl generates a Data URL representation of a barcode.
  • getBarcodeSafeUrl generates a barcode as a SafeUrl.
  • getBarcodeImageBitmap generates an ImageBitmap representation of a barcode.

Supported barcode formats are "Aztec", "Codabar", "Code128", "Code39", "Code93", "DataMatrix", "EAN-13", "EAN-8", "ITF", "PDF417", "QRCode", "UPC-A", "UPC-E" and others.

All service methods take the text to encode as a string and optional writer options as parameters. The next important writer option is the format of the barcode. All pseudo-constants are defined in the WriteInputBarcodeFormat enum.

All service methods are async and return a promise that resolves to the barcode or QR code representation.

The source code is depicted below. It should be self-explanatory.

import { inject, Injectable } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { writeBarcode, WriterOptions } from "zxing-wasm/writer";
/**
 * Service to generate barcode images using ZXing WASM
 */
@Injectable({
  providedIn: 'root'
})
export class BarcodeService {

  /**
   * DI
   */
  private readonly sanitizer = inject(DomSanitizer);

  /**
   * Generated barcode image as SVG
   *
   * @param text is the text to encode
   * @param writerOptions are the options for the writer
   * @returns a promise that resolves to a DataURL as SafeUrl or null
   */
  async getBarcodeSvg(text: string, writerOptions?: WriterOptions): Promise<string | null> {
    if (!text || text.length === 0) {
      return null;
    }
    try {
      const writeResult = await writeBarcode(text, writerOptions);
      if (writeResult.error) {
        return null;
      } else if (!writeResult.svg) {
        return null;
      } else {
        const res = writeResult.svg;
        return res;
      }
    } catch (error) {
      return null;
    }
  }

  /**
   * Generates a barcode in SVG format and returns it as a Blob.
   *
   * @param text - The text to encode into the barcode. Must be a non-empty string.
   * @param writerOptions - Optional configuration options for the barcode writer.
   * @returns A Promise that resolves to a Blob containing the SVG representation of the barcode,
   *          or `null` if the input text is invalid or the SVG generation fails.
   * @throws Will log an error to the console if the input text is empty or undefined.
   */
  async getBarcodeSvgAsBlob(text: string, writerOptions?: WriterOptions): Promise<Blob | null> {
    if (!text || text.length === 0) {
      return null;
    }
    const svg = await this.getBarcodeSvg(text, writerOptions);
    if (svg) {
      const blob = new Blob([svg], { type: 'image/svg+xml' });
      return blob;
    }
    return null;
  }

  /**
   * Generates a barcode in SVG format and returns it as a `SafeUrl`.
   *
   * This method takes a text input, generates a barcode in SVG format, 
   * and wraps it in a `SafeUrl` to ensure it is safe for use in Angular 
   * templates. If the input text is empty or null, the method logs an 
   * error and returns `null`.
   *
   * @param text - The text to encode in the barcode.
   * @param writerOptions - Optional configuration for the barcode writer.
   * @returns A promise that resolves to a `SafeUrl` containing the barcode SVG, 
   *          or `null` if the input text is invalid or an error occurs.
   */
  async getBarcodeSvgAsSafeUrl(text: string, writerOptions?: WriterOptions): Promise<SafeUrl | null> {
    if (!text || text.length === 0) {
      return null;
    }
    const svg = await this.getBarcodeSvg(text, writerOptions);
    if (svg) {
      const blob = new Blob([svg], { type: 'image/svg+xml' });
      return blob ? this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob)) : null;
    }
    return null;
  }

  /**
   * Generates a barcode image as a Blob from the provided text.
   *
   * @param text - The text to encode into the barcode. Must be a non-empty string.
   * @param writerOptions - Optional configuration options for the barcode writer.
   * @returns A promise that resolves to a Blob containing the barcode image, or `null` if an error occurs.
   *
   * @throws Will log an error to the console if:
   * - The `text` parameter is empty or null.
   * - The barcode generation fails due to an internal error.
   * - The generated barcode image is missing.
   */
  async getBarcodeBlob(text: string, writerOptions?: WriterOptions): Promise<Blob | null> {
    if (!text || text.length === 0) {
      return null;
    }
    try {
      const writeResult = await writeBarcode(text, writerOptions);
      if (writeResult.error) {
        return null;
      } else if (!writeResult.image) {
        return null;
      } else {
        const res = writeResult.image;
        return res;
      }
    } catch (error) {
      return null;
    }
  }

  /**
   * Generates a Data URL representation of a barcode for the given text.
   *
   * @param text - The text to encode into the barcode. Must be a non-empty string.
   * @param writerOptions - Optional configuration options for the barcode writer.
   * @returns A promise that resolves to a Data URL string representing the barcode,
   *          or `null` if the text is invalid or an error occurs during processing.
   *
   * @throws Will log an error to the console if the input text is empty or undefined.
   */
  async getBarcodeDataUrl(text: string, writerOptions?: WriterOptions): Promise<string | null> {
    if (!text || text.length === 0) {
      return null;
    }
    const blob = await this.getBarcodeBlob(text, writerOptions);
    if (blob) {
      const reader = new FileReader();
      const res = new Promise<string | null>((resolve) => {
        reader.onload = () => {
          resolve(reader.result as string);
        };
        reader.onerror = () => {
          resolve(null);
        };
      });
      reader.readAsDataURL(blob);
      return res;
    } else {
      return null;
    }
  }

  /**
   * Generates a barcode as a SafeUrl from the provided text and writer options.
   *
   * This method creates a barcode blob using the `getBarcodeBlob` method and 
   * converts it into a SafeUrl using Angular's `DomSanitizer` to bypass security 
   * restrictions. If the input text is empty or null, it logs an error and returns null.
   *
   * @param text - The text to encode into the barcode. Must be a non-empty string.
   * @param writerOptions - Optional configuration options for the barcode writer.
   * @returns A Promise that resolves to a SafeUrl containing the barcode, or null if 
   *          the input text is invalid or the barcode generation fails.
   */
  async getBarcodeSafeUrl(text: string, writerOptions?: WriterOptions): Promise<SafeUrl | null> {
    if (!text || text.length === 0) {
      return null;
    }
    const res = await this.getBarcodeBlob(text, writerOptions);
    return res ? this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(res)) : null;
  }


  /**
   * Generates an `ImageBitmap` representation of a barcode for the given text.
   *
   * @param text - The text to encode into a barcode. Must be a non-empty string.
   * @param writerOptions - Optional configuration for the barcode writer, such as format or dimensions.
   * @returns A promise that resolves to an `ImageBitmap` of the generated barcode, or `null` if the input text is invalid or an error occurs.
   *
   * @throws Will log an error to the console if the input text is empty or null.
   */
  async getBarcodeImageBitmap(text: string, writerOptions?: WriterOptions): Promise<ImageBitmap | null> {
    if (!text || text.length === 0) {
      return null;
    }
    const blob = await this.getBarcodeBlob(text, writerOptions);
    if (!blob) {
      return null;
    }
    const imageBitMap = await createImageBitmap(blob);
    return imageBitMap;
  }
}

BarcodeComponent: Rendering barcodes and QR codes

BarcodeComponent is a component rendering barcodes and QR codes. It uses the BarcodeService to generate the barcode images.

The BarcodeComponent has two required input signals:

  • text is the text to encode as barcode, type string.
  • format is the format of the barcode. It includes values "Aztec", "Codabar", "Code128", "Code39", "Code93", "DataMatrix", "EAN-13", "EAN-8", "ITF", "PDF417", "QRCode", "UPC-A", "UPC-E".

And there are also two optional input signals:

  • caption is the caption of the barcode. It can be either undefined, wrap or ellipsis. When caption is defined, the caption is displayed below the barcode.
  • contentType is the content type of the barcode. It can be either image/png or image/svg+xml. The default value is image/svg+xml.

The BarcodeComponent uses the resource API to load the barcode image. The resource API is a new feature in Angular v19 that allows you to load resources asynchronously and manage their state.

The BarcodeComponent has the resource signals image and svg. Depending on the contentType the image or svg resource is used to load the barcode image. Please note, that SVG barcode images may already include a caption.

The source code is depicted below.

import { booleanAttribute, ChangeDetectionStrategy, Component, computed, inject, input, resource } from '@angular/core';
import { WriteInputBarcodeFormat, WriterOptions } from 'zxing-wasm';
import { BarcodeService } from '../../services/barcode.service';
import { BarcodeCaptionComponent } from '../barcode-caption/barcode-caption.component';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';

/**
 * Component to display a barcode image
 */
@Component({
  selector: 'app-barcode',
  imports: [
    BarcodeCaptionComponent,
    MatButtonModule,
    MatIconModule
  ],
  templateUrl: './barcode.component.html',
  styleUrl: './barcode.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BarcodeComponent {
  /** DI */
  #barcodeService = inject(BarcodeService);
  /**
   * Required input signal providing the text to encode as string
   */
  text = input.required<string>();
  /**
   * Optional input providing caption and caption type
   */
  caption = input<'wrap' | 'ellipsis' | undefined>(undefined);
  /**
   * Optional input providing content type
   * - Defaults to 'image/svg+xml'
   */
  contentType = input<'image/png' | 'image/svg+xml'>('image/svg+xml');
  /**
   * Required input signal providing the format of the barcode
   */
  format = input.required<WriteInputBarcodeFormat>();
  
  /**
   * Barcode image resource, a DataURL as SafeUrl, PNG based
   */
  image = resource({
    request: () => {
      const writerOptions: WriterOptions = { format: this.format(), sizeHint: 140, withHRT: true, withQuietZones: true };
      if (this.format() === 'QRCode') {
        writerOptions.ecLevel = 'H';
      }
      return { text: this.text(), writerOptions }
    },

    loader: ({ request }) => {
      return this.#barcodeService.getBarcodeSafeUrl(request.text, request.writerOptions);
    }
  });

  /**
   * Barcode svg resource
   */
  svg = resource({
    request: () => {
      const writerOptions: WriterOptions = { format: this.format(), sizeHint: 140, withHRT: true, withQuietZones: true };
      if (this.format() === 'QRCode') {
        writerOptions.ecLevel = 'H';
      }
      return { text: this.text(), writerOptions }
    },

    loader: ({ request }) => {
      return this.#barcodeService.getBarcodeSvgAsSafeUrl(request.text, request.writerOptions);
    }
  });
}

The template of the BarcodeComponent is simple and depicted below.

<div class="barcode-container">
    <div class="barcode">
        @if(contentType() === 'image/svg+xml') {
        <img [src]="svg.value()" alt="barcode" style="width:180px;height: auto;" />
        } @else {
        <img [src]="image.value()" alt="barcode" style="width:180px;height: auto;" />
        }
    </div>
    @if(caption()) {
    <app-barcode-caption [text]="text()" [type]="caption()!"></app-barcode-caption>
    }
</div>

Potential issue: compilation errors

In some projects using zxing-wasm you may encounter compilation errors like the one below.

✘ [ERROR] TS2304: Cannot find name 'EmscriptenModule'. [plugin angular-compiler]

    node_modules/zxing-wasm/dist/cjs/share.d.ts:7:43:
      7 │ export interface ZXingReaderModule extends EmscriptenModule {

In such cases considerate to update the tsconfig.app.json file of your project to include the following compiler options:

{
  ...
  "compilerOptions": {
   ...
    "types": ["node", "@types/emscripten"]
  },
  ...
}

The package @types/emscripten is a type definition package for Emscripten, which is a toolchain for compiling C/C++ code to WebAssembly. It provides type definitions for the Emscripten API, which is used by zxing-wasm to interact with WebAssembly modules. it usually gets installed automatically when you install zxing-wasm.

AppComponent: Using the BarcodeComponent

The AppComponent is the root component of the Angular demo application. It uses the BarcodeComponent to render barcodes and QR codes to the DOM.

The standalone component imports the BarcodeComponent and uses it in the HTML template to render barcodes and QR codes for all available formats.

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { BarcodeComponent } from './components/barcode/barcode.component';

@Component({
    selector: 'app-root',
    imports: [BarcodeComponent],
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss',
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
}

The HTML template of the AppComponent below depicts the usage of BarcodeComponent.

It renders barcodes and QR codes for available formats to the DOM.

<app-barcode text="The quick brown fox jumps over the lazy dog" format="Aztec"/>
<app-barcode text="31117013206375" format="Codabar"/>
<app-barcode text="Angular" format="Code128"/>
<app-barcode text="Material" format="Code39"/>
<app-barcode text="Design" format="Code93"/>
<app-barcode text="Reactive" format="DataMatrix"/>
<app-barcode text="4001513007704" format="EAN-13"/>
<app-barcode text="96385074" format="EAN-8"/>
<app-barcode text="1234567895" format="ITF"/>
<app-barcode text="https://advenage.com" format="QRCode"/>
<app-barcode text="03600029145" format="UPC-A"/>
<app-barcode text="02345673" format="UPC-E"/>
<app-barcode text="4001513007704" format="PDF417"/>

With some simple styling, the result in the browser looks like depicted below.