How to Build an Angular Barcode & QR Code Detection App: A Step-by-Step Guide

From 9.x to 10.x, the Dynamsoft JavaScript barcode SDK has undergone a complete redesign. The new version not only provides essential barcode scanning functionalities but also integrates seamlessly with other Dynamsoft products, such as Dynamsoft Label Recognizer, Dynamsoft Camera Enhancer, Dynamsoft Document Normalizer, and Dynamsoft Code Parser. All Dynamsoft products are now unified under the Dynamsoft Capture Vision architecture. In this article, we will demonstrate how to build Angular barcode and QR code scanner apps from scratch using the Dynamsoft JavaScript Barcode SDK v10.x.

Angular 1D/2D Barcode Scanner

Online Demo

Try the Demo

Prerequisites

  1. Node.js
  2. Install Angular CLI for Angular project development:

     npm install -g @angular/cli
     ng --version
    
  3. Obtain a 30-Day Trial License for Dynamsoft JavaScript Barcode SDK v10.x.

Step1: Scaffold an Angular Barcode and QR Code Scanner Project

Create a New Angular Project

First, create a new Angular project named angular-barcode-qr-code-scanner using the Angular CLI:

ng new angular-barcode-qr-code-scanner

Install Required Packages

Next, install the necessary Dynamsoft packages via npm:

npm i dynamsoft-barcode-reader-bundle dynamsoft-image-processing dynamsoft-capture-vision-std
  • dynamsoft-barcode-reader-bundle: The package organizes different versions of Dynamsoft JavaScript products to ensure compatibility.

    Dynamsoft JavaScript barcode SDK v10.x

  • dynamsoft-image-processing: The package provides image processing functionalities.
  • dynamsoft-capture-vision-std: The package is a collection of classes and functions that facilitate the execution of other modules within the Dynamsoft Capture Vision architecture.

Configure Angular to Use Third-Party JavaScript Libraries

To make the third-party JavaScript libraries work in Angular, follow these steps:

  1. Open the angular.json file and add the following configuration under the build section:

     "build": 
     {
       "builder": "@angular-devkit/build-angular:browser",
       "options": {
         ...
         "assets": [
           "src/favicon.ico",
           "src/assets",
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-capture-vision-std/dist",
             "output": "assets/dynamsoft-capture-vision-std"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-image-processing/dist",
             "output": "assets/dynamsoft-image-processing"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-core/dist",
             "output": "assets/dynamsoft-core"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-license/dist",
             "output": "assets/dynamsoft-license"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-capture-vision-router/dist",
             "output": "assets/dynamsoft-capture-vision-router"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-barcode-reader/dist",
             "output": "assets/dynamsoft-barcode-reader"
           },
           {
             "glob": "**/*",
             "input": "./node_modules/dynamsoft-camera-enhancer/dist",
             "output": "assets/dynamsoft-camera-enhancer"
           }
         ...
         ],
       },
       ...
     },
    
  2. In your TypeScript code, set the resource output paths as follows:

     import { CoreModule, LicenseManager } from 'dynamsoft-barcode-reader-bundle';
        
     CoreModule.engineResourcePaths = {
       std: 'assets/dynamsoft-capture-vision-std/',
       dip: 'assets/dynamsoft-image-processing/',
       core: 'assets/dynamsoft-core/',
       license: 'assets/dynamsoft-license/',
       cvr: 'assets/dynamsoft-capture-vision-router/',
       dbr: 'assets/dynamsoft-barcode-reader/',
       dce: 'assets/dynamsoft-camera-enhancer/',
     };
    

Step 2: Create Angular Components for Setting License Key and Managing License Status

In this project, we will provide an input element for users to set a valid license key. Once the license key passes validation, the status is shared globally among different components: barcode reader and barcode scanner.

  1. Generate a component and a shared service:

     ng generate component product-list
     ng generate service shared
    
  2. Update the shared service to store the boolean value in the shared.service.ts file:

     import { Injectable } from '@angular/core';
    
     @Injectable({
       providedIn: 'root'
     })
     export class SharedService {
       private showDiv: boolean = false;
        
       getShowDiv(): boolean {
         return this.showDiv;
       }
        
       setShowDiv(value: boolean): void {
         this.showDiv = value;
       }
        
       toggleShowDiv(): void {
         this.showDiv = !this.showDiv;
       }
     }
        
    
  3. Update the component to use the shared service in the product-list.component.ts file:

     import { Component } from '@angular/core';
    
     import { products } from '../products';
        
     import { CoreModule, LicenseManager } from 'dynamsoft-barcode-reader-bundle';
        
     import { SharedService } from '../shared.service';
        
     @Component({
       selector: 'app-product-list',
       templateUrl: './product-list.component.html',
       styleUrls: ['./product-list.component.css'],
     })
     export class ProductListComponent {
       products = products;
       inputText: string = '';
       processedText: string = '';
       placeholderText: string = 'DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==';
        
       constructor(private sharedService: SharedService) { }
        
       async activate(): Promise<void> {
         this.processedText = this.inputText.toUpperCase();
         // Configure the paths where the .wasm files and other necessary resources for modules are located.
         CoreModule.engineResourcePaths = {
           std: 'assets/dynamsoft-capture-vision-std/',
           dip: 'assets/dynamsoft-image-processing/',
           core: 'assets/dynamsoft-core/',
           license: 'assets/dynamsoft-license/',
           cvr: 'assets/dynamsoft-capture-vision-router/',
           dbr: 'assets/dynamsoft-barcode-reader/',
           dce: 'assets/dynamsoft-camera-enhancer/',
         };
        
         try {
           // Visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=github&product=dbr to get a trial license.
           let licenseKey: string = this.inputText === '' ? this.placeholderText : this.inputText;
           await LicenseManager.initLicense(licenseKey, true);
           CoreModule.loadWasm(['DBR']);
        
           this.toggleDivVisibility();
         } catch (error) {
           alert(error);
         }
        
       }
        
       toggleDivVisibility(): void {
         this.sharedService.toggleShowDiv();
       }
        
       get showDiv(): boolean {
         return this.sharedService.getShowDiv();
       }
     }
        
    

    Explanation:

    • The activate method is triggered when the user clicks the Activate button. It initializes the Dynamsoft JavaScript Barcode SDK with the license key.
    • The license key stored in the placeholder text is a one-day trial license key publicly available for all users.
    • The showDiv property is used to toggle the visibility of barcode reader and scanner components after the license key is validated.
  4. Update the component template in the product-list.component.html file:

     <div>
       <div>
         Get a License key from <a href="https://www.dynamsoft.com/customer/license/trialLicense" target="_blank">here</a>
       </div>
       <input type="text" [(ngModel)]="inputText" [placeholder]="placeholderText">
       <button (click)="activate()">Activate SDK</button>
     </div>
        
     <div *ngIf="showDiv">
       <h2>Examples</h2>
       <div *ngFor="let product of products">
         <h3>
           <div *ngIf="product.id === 'reader'; else elseBlock">
             <a [title]="product.name + ' details'" [routerLink]="['/barcode-reader']">>
                  
             </a>
           </div>
           <ng-template #elseBlock><a [title]="product.name + ' details'" [routerLink]="['/barcode-scanner']">>
                  
             </a></ng-template>
         </h3>
        
         <p *ngIf="product.description">Description: </p>
       </div>
     </div>
    

    Explanation:

    • The input element is used to set the license key.
    • The button element is used to trigger the activate method.
    • The *ngIf directive is used to toggle the visibility of the barcode reader and scanner components.

Step3: Implement Angular Barcode Reader

  1. Create a barcode-reader component via Angular CLI:

     ng generate component barcode-reader
    
  2. Construct the web page layout in the barcode-reader.component.html file:

     <input type="file" id="file" accept="image/*" (change)="onChange($event)" />
     <div>
       <a id="result"></a>
     </div>
        
     <div id="imageview">
       <img id="image" />
       <canvas id="overlay"></canvas>
     </div>
    

    Explanation

    • The input element is used to select an image file.
    • The a element is used to display barcode and QR code results.
    • The img element is used to display the selected image.
    • The canvas element is used to draw barcode and QR code results.
  3. Add TypeScript code to the barcode-reader.component.ts file.

     import { Component, OnInit } from '@angular/core';
     import { OverlayManager } from '../overlay';
     import { CapturedResult, CaptureVisionRouter, BarcodeResultItem, EnumCapturedResultItemType } from 'dynamsoft-barcode-reader-bundle';
        
     @Component({
       selector: 'app-barcode-reader',
       templateUrl: './barcode-reader.component.html',
       styleUrls: ['./barcode-reader.component.css'],
     })
     export class BarcodeReaderComponent implements OnInit {
       isLoaded = false;
       overlay: HTMLCanvasElement | undefined;
       context: CanvasRenderingContext2D | undefined;
       overlayManager: OverlayManager;
       cvr: CaptureVisionRouter | undefined;
        
       constructor() {
         this.overlayManager = new OverlayManager();
       }
        
       ngOnInit(): void {
         this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
         (async () => {
           this.cvr = await CaptureVisionRouter.createInstance();
           this.isLoaded = true;
         })();
       }
        
       onChange(event: Event) {
         const element = event.currentTarget as HTMLInputElement;
         let fileList: FileList | null = element.files;
         if (fileList) {
           let file = fileList.item(0) as any;
           if (file) {
             let fr = new FileReader();
             fr.onload = (event: any) => {
               let image = document.getElementById('image') as HTMLImageElement;
               if (image) {
                 image.src = event.target.result;
                 const img = new Image();
        
                 img.onload = (event: any) => {
                   this.overlayManager.updateOverlay(img.width, img.height);
                   if (this.cvr) {
        
                     this.cvr.capture(file, 'ReadBarcodes_Balance').then((result: CapturedResult) => {
                       console.log(result);
                       let txts: any = [];
                       let elem = document.getElementById('result');
                       try {
                         let localization;
                         let items = result.items
                         if (items.length > 0) {
                           for (var i = 0; i < items.length; ++i) {
        
                             if (items[i].type !== EnumCapturedResultItemType.CRIT_BARCODE) {
                               continue; // check if captured result item is a barcode
                             }
        
                             let item = items[i] as BarcodeResultItem;
        
                             txts.push(item.text);
                             localization = item.location;
                             console.log(localization);
                             this.overlayManager.drawOverlay(
                               localization,
                               item.text
                             );
                           }
        
                           if (elem) {
                             elem.innerHTML = txts.join(', ');
                           }
                         } else {
                           if (elem) {
                             elem.innerHTML = txts.join(', ');
                           }
                         }
                       } catch (e) {
                         alert(e);
                       }
                     });
                   }
                 };
                 img.src = event.target.result;
               }
             };
             fr.readAsDataURL(file);
           }
         }
       }
     }
        
    

    Explanation

    • The ngOnInit method initializes the overlay and CaptureVisionRouter object.
    • The onChange method is triggered when the user selects an image file. It reads the barcode and QR code results from the image file.

    Angular barcode and QR code reader

Step4: Implement Angular Barcode Scanner

  1. Create a barcode-scanner component via Angular CLI:

     ng generate component barcode-scanner
    
  2. Construct the web page layout in the barcode-scanner.component.html file:

     <div class="select">
       <label for="videoSource">Video source: </label>
       <select id="videoSource" (change)="openCamera()"></select>
     </div>
        
     <div id="videoview">
       <div class="dce-video-container" id="videoContainer"></div>
       <canvas id="overlay"></canvas>
     </div>
    

    Explanation

    • The select element is used to list the available video sources.
    • The div element with the videoContainer id is used to display the video stream.
    • The canvas element with the overlay id is used to draw barcode and QR code results.
  3. Add TypeScript code to the barcode-scanner.component.ts file:

     import { Component, OnInit } from '@angular/core';
     import { OverlayManager } from '../overlay';
     import { BarcodeResultItem, CameraEnhancer, CameraView, CapturedResult, CaptureVisionRouter, EnumCapturedResultItemType, MultiFrameResultCrossFilter, Resolution } from 'dynamsoft-barcode-reader-bundle';
        
     const componentDestroyedErrorMsg = 'VideoCapture Component Destroyed';
        
     @Component({
       selector: 'app-barcode-scanner',
       templateUrl: './barcode-scanner.component.html',
       styleUrls: ['./barcode-scanner.component.css'],
     })
     export class BarcodeScannerComponent implements OnInit {
       isLoaded = false;
       overlay: HTMLCanvasElement | undefined;
       context: CanvasRenderingContext2D | undefined;
       cameraInfo: any = {};
       videoSelect: HTMLSelectElement | undefined;
       overlayManager: OverlayManager;
       cvr?: CaptureVisionRouter;
       cameraEnhancer?: CameraEnhancer;
       isDestroyed = false;
        
       constructor() {
         this.overlayManager = new OverlayManager();
       }
        
       ngOnInit(): void {
         this.videoSelect = document.querySelector('select#videoSource') as HTMLSelectElement;
         this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
         (async () => {
           await this.initBarcodeScanner();
         })();
       }
        
       ngOnDestroy(): void {
         this.isDestroyed = true;
         try {
           this.cvr?.dispose();
           this.cameraEnhancer?.dispose();
         } catch (_) { }
       }
        
       updateResolution(): void {
         if (this.cameraEnhancer && this.overlayManager) {
           let resolution: Resolution = this.cameraEnhancer.getResolution();
           this.overlayManager.updateOverlay(resolution.width, resolution.height);
         }
       }
        
       async initBarcodeScanner(): Promise<void> {
         const cameraView: CameraView = await CameraView.createInstance();
        
         this.cameraEnhancer = await CameraEnhancer.createInstance(cameraView);
        
         this.isLoaded = true;
         let uiElement = document.getElementById('videoContainer');
         if (uiElement) {
           uiElement.append(cameraView.getUIElement());
        
           cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-camera')?.setAttribute('style', 'display: none');
           cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-resolution')?.setAttribute('style', 'display: none');
        
           let cameras = await this.cameraEnhancer.getAllCameras();
           this.listCameras(cameras);
        
        
           this.cvr = await CaptureVisionRouter.createInstance();
           if (this.isDestroyed) {
             throw Error(componentDestroyedErrorMsg);
           }
           this.cvr.setInput(this.cameraEnhancer);
        
           // Define a callback for results.
           this.cvr.addResultReceiver({
             onCapturedResultReceived: (result: CapturedResult) => {
               this.overlayManager.clearOverlay();
               let txts: any = [];
               let resultElement = document.getElementById('result');
               try {
                 let localization;
                 let items = result.items
                 if (items.length > 0) {
                   for (var i = 0; i < items.length; ++i) {
        
                     if (items[i].type !== EnumCapturedResultItemType.CRIT_BARCODE) {
                       continue; // check if captured result item is a barcode
                     }
        
                     let item = items[i] as BarcodeResultItem;
        
                     txts.push(item.text);
                     localization = item.location;
                     console.log(localization);
                     this.overlayManager.drawOverlay(
                       localization,
                       item.text
                     );
                   }
        
                   if (resultElement) {
                     resultElement.innerHTML = txts.join(', ');
                   }
                 } else {
        
                   if (resultElement) {
                     resultElement.innerHTML = txts.join(', ');
                   }
                 }
        
               } catch (e) {
                 alert(e);
               }
             },
           });
        
           this.cvr.addResultReceiver({
             onDecodedBarcodesReceived: (result) => {
               if (!result.barcodeResultItems.length) return;
        
               console.log(result);
             },
           });
        
           this.cameraEnhancer.on('played', () => {
             this.updateResolution();
           });
           await this.openCamera();
           if (this.isDestroyed) {
             throw Error(componentDestroyedErrorMsg);
           }
           await this.cvr.startCapturing('ReadSingleBarcode');
           if (this.isDestroyed) {
             throw Error(componentDestroyedErrorMsg);
           }
         }
       }
        
       async openCamera(): Promise<void> {
         this.overlayManager.clearOverlay();
         if (this.videoSelect) {
           let deviceId = this.videoSelect.value;
           if (this.cameraEnhancer) {
             await this.cameraEnhancer.selectCamera(deviceId);
             await this.cameraEnhancer.open();
           }
         }
        
       }
        
       listCameras(deviceInfos: any): void {
         for (var i = 0; i < deviceInfos.length; ++i) {
           var deviceInfo = deviceInfos[i];
           var option = document.createElement('option');
           option.value = deviceInfo.deviceId;
           option.text = deviceInfo.label;
           this.cameraInfo[deviceInfo.deviceId] = deviceInfo;
           if (this.videoSelect) this.videoSelect.appendChild(option);
         }
       }
     }
        
    

    Explanation:

    • The ngOnInit method initializes the overlay, CameraView, CaptureVisionRouter and CameraEnhancer objects.
    • The updateResolution method updates the overlay size according to the camera resolution.
    • The openCamera opens the selected camera source.
    • The listCameras method lists the available camera sources.
    • The ngOnDestroy method destroys the CaptureVisionRouter and CameraEnhancer objects when the component is destroyed.
  4. Run the Angular project:

     ng serve --ssl
    

    Angular barcode and QR code scanner

Step 5: GitHub Page Deployment

Deploying your Angular project to GitHub Pages can be automated using GitHub Actions. Follow these steps to set up the deployment process:

  1. Create a GitHub Actions workflow file to automate the build and deployment process. This workflow uses the Angular Deploy gh-pages Actions:

     name: Build and Deploy
     on:
       push:
         branches:
           - main
     jobs:
       build:
        
         runs-on: ubuntu-latest
        
         steps:
         - uses: actions/checkout@v2
         - name: All things angular
           uses: AhsanAyaz/angular-deploy-gh-pages-actions@v1.3.2
           with:
             github_access_token: $ 
             build_configuration: production 
             base_href: /angular-barcode-qr-code-scanner/   
             deploy_branch: gh-pages 
             angular_dist_build_folder: dist/angular-barcode-qr-code-scanner
        
    

    Note: Replace angular-barcode-qr-code-scanner with your actual project name.

  2. After setting up the workflow file, enable GitHub Pages for your repository:

    1. Go to Your GitHub repository > Settings > Pages.
    2. Select the gh-pages branch as the source for GitHub Pages.
    3. Save the changes to launch the GitHub page.

Q&A

Q: What if you encounter the following error on Windows?

ng : File C:\Users\Admin\AppData\Roaming\npm\ng.ps1 cannot be loaded because running scripts is disabled on this system
. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.

A: The workaround is to temporarily bypass the execution policy for the current PowerShell session by running the following command:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

Source Code

https://github.com/yushulx/angular-barcode-qr-code-scanner