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 anImageBitmap
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 eitherundefined
,wrap
orellipsis
. Whencaption
is defined, the caption is displayed below the barcode.contentType
is the content type of the barcode. It can be eitherimage/png
orimage/svg+xml
. The default value isimage/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.
