# @isa/shared/scanner Enterprise-grade barcode scanning library for ISA-Frontend using the Scandit SDK, providing mobile barcode scanning capabilities for iOS and Android platforms. ## Overview The `@isa/shared/scanner` library provides a complete, production-ready solution for mobile barcode scanning with comprehensive support for multiple symbologies, platform detection, ready-state management, and seamless integration with Angular's reactive patterns. Built on the [Scandit SDK](https://www.scandit.com/), it offers ready-to-use components, directives, and services for implementing barcode scanning workflows throughout the ISA application. **Key Use Cases:** - Product lookup and inventory management - Order processing and verification - Returns and remission workflows - Asset tracking and identification - QR code scanning for digital workflows **Type:** Shared component library (UI + Services) ## Installation ```ts import { ScannerService, ScannerButtonComponent, ScannerReadyDirective } from '@isa/shared/scanner'; ``` ## Features - **Multiple Symbologies:** EAN-8, EAN-13/UPC-A, UPC-E, Code 128, Code 39, Code 93, Interleaved 2 of 5, QR Code - **Platform Detection:** Automatic iOS/Android detection with unsupported platform handling - **Ready-State Management:** Reactive signals for scanner initialization status - **Overlay UI:** Full-screen camera overlay with CDK integration - **Form Integration:** Output bindings for Angular forms - **Configurable:** Injection tokens for license key, library location, and symbologies - **TypeScript:** Full type safety with Zod schema validation - **Logging:** Integrated with `@isa/core/logging` for debugging - **E2E Testing:** E2E attributes for automated testing (`data-which="scan-button"`) ## API Reference ### Services #### `ScannerService` Core service that manages barcode scanning functionality using the Scandit SDK. **Properties:** - `ready: Signal` - Computed signal indicating whether the scanner is initialized and ready to use. Automatically triggers configuration on first access. **Methods:** - `configure(): Promise` - Manually configure the Scandit SDK. Called automatically by `open()` and the `ready` signal. Handles platform compatibility checks and license validation. - `open(options?: ScannerInputs): Promise` - Opens the scanner overlay interface and returns a promise that resolves to the scanned barcode value. Returns `null` if the user cancels scanning. **Configuration Options (`ScannerInputs`):** ```ts type ScannerInputs = { symbologies?: Symbology[]; // Override default barcode types abortSignal?: AbortSignal; // Cancel scanning operation }; ``` **Scanner Status:** The service tracks initialization through the following states: - `None` - Initial state, not yet initialized - `Initializing` - Configuration in progress - `Ready` - Scanner fully initialized and ready for use - `Error` - Initialization failed **Platform Support:** Only iOS and Android platforms are supported. Attempting to use the scanner on unsupported platforms will log a warning and prevent initialization. The service throws `PlatformNotSupportedError` for unsupported platforms. **Default Symbologies:** The scanner recognizes the following barcode formats by default: - EAN-8 - EAN-13 / UPC-A - UPC-E - Code 128 - Code 39 - Code 93 - Interleaved 2 of 5 - QR Code ### Components #### `ScannerButtonComponent` A ready-to-use button component that triggers the barcode scanner when clicked. Automatically handles scanner lifecycle and cleanup. **Selector:** `shared-scanner-button` **Inputs:** - `size: InputSignal` - Button size (default: `'large'`) - `disabled: ModelSignal` - Whether the button is disabled (default: `false`) **Outputs:** - `scan: OutputEmitterRef` - Emits the scanned barcode value or `null` if the user cancels **Features:** - Only visible when scanner is ready (uses `*sharedScannerReady` directive internally) - Opens full-screen scanner overlay on click - Automatically aborts scanning operations on component destroy - Includes E2E testing attribute: `data-which="scan-button"` - Primary color styling with scanner icon **Lifecycle:** - Implements `OnDestroy` to abort any in-progress scanning operations - Uses `AbortController` for proper cleanup #### `ScannerComponent` Internal component that renders the camera view and handles barcode detection. Used by `ScannerService` via CDK overlay and typically not used directly in application code. **Selector:** `shared-scanner` **Inputs:** - `symbologies: InputSignal` - Array of barcode types to detect **Outputs:** - `scan: OutputEmitterRef` - Emits detected barcode data **Features:** - Full-screen camera view with close button - Progress indicator during initialization - Zone-aware event handling for optimal change detection - Automatic camera lifecycle management (on/off) - Cleanup on destroy to prevent memory leaks ### Directives #### `ScannerReadyDirective` Structural directive that conditionally renders content based on scanner ready state. Similar to `*ngIf`, but specifically tied to scanner initialization status. **Selector:** `*sharedScannerReady` **Inputs:** - `scannerReadyElse: InputSignal | undefined>` - Optional template to show when scanner is not ready (similar to `*ngIf` else template) **Features:** - Reactive to scanner status changes using Angular effects - Supports else template for fallback content - Automatically updates when scanner becomes ready - No manual subscription management required ### Injection Tokens #### `SCANDIT_LICENSE` Injection token for the Scandit license key. **Default:** Retrieves from application config at `licence.scandit` path **Type:** `InjectionToken` #### `SCANDIT_LIBRARY_LOCATION` Injection token for the Scandit SDK library location. **Default:** `/scandit` relative to `document.baseURI` **Type:** `InjectionToken` #### `SCANDIT_DEFAULT_SYMBOLOGIES` Injection token for the default barcode symbologies to scan. **Default:** Array of common symbologies (EAN, UPC, Code128, Code39, Code93, ITF, QR) **Type:** `InjectionToken` ### Errors #### `PlatformNotSupportedError` Thrown when attempting to use the scanner on non-mobile platforms. ```ts export class PlatformNotSupportedError extends Error { constructor(); // Message: "ScannerService is only supported on iOS and Android platforms" } ``` **Behavior:** - Caught by `ScannerService.configure()` and logged as a warning - Prevents scanner initialization on unsupported platforms - Does not crash the application ## Usage Examples ### Basic Programmatic Scanning ```ts import { Component, inject } from '@angular/core'; import { ScannerService } from '@isa/shared/scanner'; @Component({ selector: 'app-checkout', template: ` @if (scannedCode) {

Scanned: {{ scannedCode }}

} ` }) export class CheckoutComponent { #scannerService = inject(ScannerService); scannedCode: string | null = null; async scanProduct() { try { this.scannedCode = await this.#scannerService.open(); if (this.scannedCode) { console.log('Product code:', this.scannedCode); // Process the scanned code (e.g., fetch product details) } } catch (error) { console.error('Scanning failed:', error); } } } ``` ### Using Scanner Button Component ```ts import { Component } from '@angular/core'; import { ScannerButtonComponent } from '@isa/shared/scanner'; @Component({ selector: 'app-inventory', imports: [ScannerButtonComponent], template: `

Inventory Lookup

@if (lastScan) {

Last scanned: {{ lastScan }}

Product: {{ productName }}

} ` }) export class InventoryComponent { lastScan: string | null = null; productName: string = ''; isProcessing = false; async onScan(code: string | null) { if (code) { this.lastScan = code; this.isProcessing = true; await this.processBarcode(code); this.isProcessing = false; } } private async processBarcode(code: string) { // Fetch product details from API const response = await fetch(`/api/products/${code}`); const product = await response.json(); this.productName = product.name; } } ``` ### Scanning with Custom Symbologies ```ts import { Component, inject } from '@angular/core'; import { ScannerService } from '@isa/shared/scanner'; import { Symbology } from 'scandit-web-datacapture-barcode'; @Component({ selector: 'app-qr-scanner', template: ` ` }) export class QRScannerComponent { #scannerService = inject(ScannerService); async scanQRCode() { // Only scan QR codes const result = await this.#scannerService.open({ symbologies: [Symbology.QR] }); if (result) { this.handleQRCode(result); } } async scanEAN() { // Only scan EAN barcodes const result = await this.#scannerService.open({ symbologies: [Symbology.EAN8, Symbology.EAN13UPCA] }); if (result) { this.handleProductCode(result); } } private handleQRCode(data: string) { console.log('QR Code:', data); } private handleProductCode(code: string) { console.log('Product Code:', code); } } ``` ### Using Scanner Ready Directive ```ts import { Component } from '@angular/core'; import { ScannerButtonComponent, ScannerReadyDirective } from '@isa/shared/scanner'; @Component({ selector: 'app-product-lookup', imports: [ScannerButtonComponent, ScannerReadyDirective], template: `

Scan Product Barcode

Point your camera at a barcode to scan

Camera scanner is not available on this device

Please enter the barcode manually:

` }) export class ProductLookupComponent { onScan(code: string | null) { if (code) { this.lookupProduct(code); } } onManualEntry(event: Event) { const input = event.target as HTMLInputElement; if (input.value.length >= 8) { this.lookupProduct(input.value); } } private lookupProduct(barcode: string) { console.log('Looking up product:', barcode); // Fetch product by barcode } } ``` ### Cancelling a Scan Operation ```ts import { Component, inject, OnDestroy } from '@angular/core'; import { ScannerService } from '@isa/shared/scanner'; @Component({ selector: 'app-advanced-scanner', template: ` @if (errorMessage) {

{{ errorMessage }}

} ` }) export class AdvancedScannerComponent implements OnDestroy { #scannerService = inject(ScannerService); #abortController = new AbortController(); isScanning = false; errorMessage = ''; async startScan() { this.isScanning = true; this.errorMessage = ''; try { const result = await this.#scannerService.open({ abortSignal: this.#abortController.signal }); if (result) { console.log('Scanned:', result); } else { console.log('Scan was cancelled by user'); } } catch (error) { this.errorMessage = 'Scan was cancelled or failed'; console.error('Scan error:', error); } finally { this.isScanning = false; } } cancelScan() { this.#abortController.abort(); this.#abortController = new AbortController(); // Create new controller for next scan } ngOnDestroy() { this.#abortController.abort(); } } ``` ### Checking Scanner Readiness ```ts import { Component, inject, effect } from '@angular/core'; import { ScannerService } from '@isa/shared/scanner'; @Component({ selector: 'app-scanner-status', template: `
@if (scannerReady()) { Scanner Ready } @else { Initializing Scanner... }
` }) export class ScannerStatusComponent { #scannerService = inject(ScannerService); scannerReady = this.#scannerService.ready; constructor() { effect(() => { if (this.scannerReady()) { console.log('Scanner initialized successfully'); // Perform actions when scanner becomes ready } }); } } ``` ## Configuration ### Basic Configuration The library requires a Scandit license key configured in your application config: ```json { "licence": { "scandit": "YOUR_SCANDIT_LICENSE_KEY" } } ``` The Scandit SDK library files must be available at `/scandit/` relative to your application's base URI. ### Custom Configuration via Injection Tokens Override default configuration by providing custom values: ```ts import { SCANDIT_LICENSE, SCANDIT_LIBRARY_LOCATION, SCANDIT_DEFAULT_SYMBOLOGIES } from '@isa/shared/scanner'; import { Symbology } from 'scandit-web-datacapture-barcode'; // In your providers array (e.g., app.config.ts): export const appConfig: ApplicationConfig = { providers: [ // Custom license key { provide: SCANDIT_LICENSE, useValue: 'YOUR-CUSTOM-LICENSE-KEY' }, // Custom library location (e.g., CDN) { provide: SCANDIT_LIBRARY_LOCATION, useValue: 'https://cdn.example.com/scandit/' }, // Custom default symbologies (only QR and Code128) { provide: SCANDIT_DEFAULT_SYMBOLOGIES, useValue: [Symbology.QR, Symbology.Code128] } ] }; ``` ## Dependencies ### Core Dependencies - **@angular/core** - Angular framework (signals, effects, DI) - **@angular/cdk** - CDK platform detection and overlay - **scandit-web-datacapture-core** - Scandit SDK core functionality - **scandit-web-datacapture-barcode** - Scandit barcode scanning module - **zod** - Configuration schema validation ### ISA Dependencies - **@isa/core/config** - Application configuration management - **@isa/core/logging** - Logging utilities with factory pattern - **@isa/ui/buttons** - Icon button component - **@isa/icons** - Icon library (scanner and close icons) ## Error Handling The scanner library provides comprehensive error handling: ### Platform Compatibility ```ts // Automatically handled by ScannerService if (!platform.IOS && !platform.ANDROID) { // Logs warning and prevents initialization // Does not crash the application } ``` ### Configuration Errors ```ts try { await scannerService.configure(); } catch (error) { // Configuration errors are logged and set status to 'Error' console.error('Scanner configuration failed:', error); } ``` ### Scan Operation Errors ```ts try { const result = await scannerService.open(); } catch (error) { // Handle abort or configuration errors if (error.message === 'Scanner aborted') { console.log('User cancelled scanning'); } else { console.error('Scanning failed:', error); } } ``` ## Architecture Notes - **Signals & Effects:** Uses Angular signals for reactive state management and effects for side effect handling - **CDK Overlay:** Integrates with Angular CDK Overlay for full-screen scanner UI with backdrop and positioning - **Camera Lifecycle:** Automatic camera management (on/off state) with proper cleanup - **Zone Management:** Zone-aware event handling for optimal change detection performance - **Standalone:** All components and directives are standalone for easy tree-shaking - **Logging:** Comprehensive logging with `@isa/core/logging` factory pattern - **Memory Safety:** Proper cleanup in `OnDestroy` lifecycle hooks to prevent memory leaks ## Platform Requirements - **Supported Platforms:** iOS and Android only - **Scandit License:** Valid Scandit SDK license key required - **Camera Permissions:** Application must request and obtain camera permissions - **Library Files:** Scandit SDK files must be accessible at configured location ## E2E Testing The scanner components include E2E testing attributes for automated test selection: - `ScannerButtonComponent` includes `data-which="scan-button"` attribute - Use this attribute to locate and interact with the scanner button in E2E tests ```ts // Example E2E test await page.click('[data-which="scan-button"]'); ```