How to Build Reusable Angular Components for Web Document Scanning

Pre-requisites

npm install -g @angular/cli

ng --version

Angular CLI: 13.3.7
Node: 16.13.1
Package Manager: npm 8.1.2
OS: win32 x64

Angular: 13.3.10
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1303.7
@angular-devkit/build-angular 13.3.7
@angular-devkit/core 13.3.7
@angular-devkit/schematics 13.3.7
@angular/cli 13.3.7
@schematics/angular 13.3.7
ng-packagr 13.3.1
rxjs 7.5.5
typescript 4.6.4
npm i dwt

Wrapping Dynamic Web TWAIN SDK with Angular Components

We scaffold a new Angular library project with the Angular CLI:

ng new my-workspace --no-create-application
cd my-workspace
ng generate library ngx-web-document-scanner
"peerDependencies": {
"dwt": "^17.3.1"
},
npm install

Create three Angular components

Next, generate three Angular components for the library. Every component consists of a TypeScript file, CSS file, and HTML file.

cd src/lib
ng generate component ngx-scanner-capture --skip-import
ng generate component ngx-camera-capture --skip-import
ng generate component ngx-document-scanner --skip-import

ngx-scanner-capture

This component is responsible for acquiring images from a TWAIN-compatible, SANE-compatible or ICA-compatible scanners. To communicate with the scanner, you need to install a local service provided by Dynamsoft on the client machine. Dynamic Web TWAIN JavaScript API will automatically detect the service and pop up the download link if the service is not installed.

<div id="scanner-capture" hidden>
<select *ngIf="useLocalService" id="sources"></select><br />
<button *ngIf="useLocalService" (click)="acquireImage()">Scan Documents</button>
<button (click)="openImage()">Load Documents</button>
<button (click)="downloadDocument()">Download Documents</button>
</div>
import { Component, EventEmitter, OnInit, Output, Input } from '@angular/core';
import { WebTwain } from 'dwt/dist/types/WebTwain';
import Dynamsoft from 'dwt';

@Component({
selector: 'ngx-scanner-capture',
templateUrl: './ngx-scanner-capture.component.html',
styleUrls: ['./ngx-scanner-capture.component.css'],
})
export class NgxScannerCaptureComponent implements OnInit {
dwtObject: WebTwain | undefined;
selectSources: HTMLSelectElement | undefined;
@Input() containerId = '';
@Input() useLocalService = false;
@Input() width = '600px';
@Input() height = '600px';
constructor() {
}

ngOnDestroy() {
Dynamsoft.DWT.Unload();
}

ngOnInit(): void {
Dynamsoft.DWT.Containers = [{ ContainerId: this.containerId, Width: this.width, Height: this.height }];
Dynamsoft.DWT.UseLocalService = this.useLocalService;
Dynamsoft.DWT.Load();
Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => { this.onReady(); });
}

onReady(): void {
this.dwtObject = Dynamsoft.DWT.GetWebTwain(this.containerId);

if (!this.useLocalService) {
this.dwtObject.Viewer.cursor = "pointer";
} else {
let sources = this.dwtObject.GetSourceNames();
this.selectSources = <HTMLSelectElement>document.getElementById("sources");
this.selectSources.options.length = 0;
for (let i = 0; i < sources.length; i++) {
this.selectSources.options.add(new Option(<string>sources[i], i.toString()));
}
}

let elem = document.getElementById('scanner-capture');
if (elem) elem.hidden = false;
}
}
acquireImage(): void {
if (!this.dwtObject) return;
if (!this.useLocalService) {
alert("Scanning is not supported under the WASM mode!");
}
else if (this.dwtObject.SourceCount > 0 && this.selectSources && this.dwtObject.SelectSourceByIndex(this.selectSources.selectedIndex)) {
const onAcquireImageSuccess = () => { if (this.dwtObject) this.dwtObject.CloseSource(); };
const onAcquireImageFailure = onAcquireImageSuccess;
this.dwtObject.OpenSource();
this.dwtObject.AcquireImage({}, onAcquireImageSuccess, onAcquireImageFailure);
} else {
alert("No Source Available!");
}
}

openImage(): void {
if (!this.dwtObject) return;
this.dwtObject.IfShowFileDialog = true;
this.dwtObject.Addon.PDF.SetConvertMode(Dynamsoft.DWT.EnumDWT_ConvertMode.CM_RENDERALL);
this.dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL,
() => {
}, () => {
});
}

downloadDocument() {
if (this.dwtObject) {
this.dwtObject.SaveAsJPEG("document.jpg", this.dwtObject.CurrentImageIndexInBuffer);
}
}

ngx-camera-capture

This component is responsible for acquiring images from a camera. All camera-relevant APIs are implemented in the wasm module. So there is no need to install a local service.

<div id="camera-capture" hidden>
<div>
<label for="videoSource"></label>
<select id="videoSource"></select><br />
<button (click)="openCamera()">Open a Camera</button>
<button (click)="captureDocument()">Capture Documents</button>
<button (click)="downloadDocument()">Download Documents</button>
</div>
</div>
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { WebTwain } from 'dwt/dist/types/WebTwain';
import Dynamsoft from 'dwt';

@Component({
selector: 'ngx-camera-capture',
templateUrl: './ngx-camera-capture.component.html',
styleUrls: ['./ngx-camera-capture.component.css']
})
export class NgxCameraCaptureComponent implements OnInit {
dwtObject: WebTwain | undefined;
videoSelect: HTMLSelectElement | undefined;
sourceDict: any = {};
@Input() containerId = '';
@Input() useLocalService = true;
@Input() width = '600px';
@Input() height = '600px';
@Input() previewId = '';

constructor() {
}

ngOnDestroy() {
Dynamsoft.DWT.Unload();
}

ngOnInit(): void {
this.videoSelect = document.querySelector('select#videoSource') as HTMLSelectElement;
Dynamsoft.DWT.Containers = [{ ContainerId: this.containerId, Width: this.width, Height: this.height }];
Dynamsoft.DWT.UseLocalService = this.useLocalService;
Dynamsoft.DWT.Load();
Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => { this.onReady(); });
}
}
openCamera() {
if (this.videoSelect) {
let index = this.videoSelect.selectedIndex;
if (index < 0) return;

var option = this.videoSelect.options[index];
if (this.dwtObject) {
this.dwtObject.Addon.Camera.selectSource(this.sourceDict[option.text]).then(camera => {
if (this.videoSelect) this.createCameraScanner(this.sourceDict[option.text]);
});
}
}

}

async createCameraScanner(deviceId: string): Promise<void> {
if (this.dwtObject) {
await this.dwtObject.Addon.Camera.closeVideo();
await this.dwtObject.Addon.Camera.play(document.getElementById(this.previewId) as HTMLDivElement);
}
}
async captureDocument() {
if (this.dwtObject) {
await this.dwtObject.Addon.Camera.capture();
}
}
async downloadDocument() {
if (this.dwtObject) {
this.dwtObject.SaveAsJPEG("document.jpg", this.dwtObject.CurrentImageIndexInBuffer);
}
}

ngx-document-scanner

This component contains camera capture and some advanced image processing features. In contrast to the ngx-camera-capture component, it calls a more advanced method scanDocument() instead of play() when initializing the camera preview.

async createCameraScanner(deviceId: string): Promise<void> {
if (this.dwtObject) {
await this.dwtObject.Addon.Camera.closeVideo();
this.dwtObject.Addon.Camera.scanDocument({
scannerViewer: {
deviceId: deviceId,
fullScreen: true,
autoDetect: {
enableAutoDetect: true
},
continuousScan: true
}

}).then(
function () { console.log("OK"); },
function (error: any) { console.log(error.message); });
}
}

Export the components

As the three components are done, we need to declare them in the projects/ngx-web-document-scanner/src/lib/ngx-web-document-scanner.module.ts file.

import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { NgxScannerCaptureComponent } from './ngx-scanner-capture/ngx-scanner-capture.component';
import { NgxCameraCaptureComponent } from './ngx-camera-capture/ngx-camera-capture.component';
import { NgxDocumentScannerComponent } from './ngx-document-scanner/ngx-document-scanner.component';
import { DocumentScannerServiceConfig } from './ngx-web-document-scanner.service';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [
NgxScannerCaptureComponent,
NgxCameraCaptureComponent,
NgxDocumentScannerComponent
],
imports: [
CommonModule
],
exports: [
NgxScannerCaptureComponent,
NgxCameraCaptureComponent,
NgxDocumentScannerComponent
]
})
import { Injectable, Optional } from '@angular/core';
import Dynamsoft from 'dwt';

export class DocumentScannerServiceConfig {
licenseKey = '';
resourcePath = '';
}

@Injectable({
providedIn: 'root'
})

export class NgxDocumentScannerService {

constructor(@Optional() config?: DocumentScannerServiceConfig) {
if (config) {
Dynamsoft.DWT.ProductKey = config.licenseKey;
Dynamsoft.DWT.ResourcesPath = config.resourcePath;
}
}
}

Publish the package

We can now build and publish the Angular library project to npm:

ng build ngx-web-document-scanner
cd dist/ngx-web-document-scanner
npm publish

NPM Package

npm i ngx-web-document-scanner

Creating Web Document Scanner Application in Angular

Let’s implement a web document scanner application in Angular within 5 minutes.

  1. Create a new Angular project:
ng create web-document-scanner
npm i ngx-web-document-scanner
ng generate component document-scanner
import { Component, OnInit } from '@angular/core';
import { NgxDocumentScannerService } from 'ngx-web-document-scanner';

@Component({
selector: 'app-document-scanner',
templateUrl: './document-scanner.component.html',
styleUrls: ['./document-scanner.component.css']
})
export class DocumentScannerComponent implements OnInit {

constructor(private documentScannerService: NgxDocumentScannerService) { }

ngOnInit(): void {
}

}
<div id="container">
<div id="dwtcontrolContainer"></div>
</div>

<ngx-scanner-capture [useLocalService]="true" [containerId]="'dwtcontrolContainer'"
[width]="'600px'" [height]="'600px'"></ngx-scanner-capture>
"build": {
"builder": "@angular-devkit/build-angular:browser",
...
"assets": [
"src/favicon.ico",
"src/assets",
{
"glob": "**/*",
"input": "./node_modules/dwt/dist",
"output": "assets/dynamic-web-twain"
}
],
...
}
import { NgxDocumentScannerModule } from 'ngx-web-document-scanner';

@NgModule({
...
imports: [
BrowserModule,
AppRoutingModule,
NgxDocumentScannerModule.forRoot({
licenseKey: "LICENSE-KEY",
resourcePath: "assets/dynamic-web-twain"}),
],
...
})
ng serve

Try the Online Demo

https://yushulx.me/angular-scanner-camera-capture/

Source Code

https://github.com/yushulx/ngx-web-document-scanner

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store