Angular使用zxing-js扫码填充FormBuilder表单提交值为空
问题根因
直接通过原生JS操作DOM给input设置value的写法,完全绕过了Angular响应式表单(FormBuilder)的状态更新机制:
- 键盘输入时,Angular内置的表单指令会监听输入事件,自动把输入内容同步到FormControl的内部状态,所以提交时能正常拿到值
- 原生
document.getElementById("codEtiqueta").value = xxx的写法只会修改DOM的显示内容,不会触发Angular表单的状态更新,FormControl存储的实际值还是空,提交自然拿不到扫码填充的内容 - 额外存在两个隐患:一是在响应式表单上混用
[(ngModel)]模板驱动写法,Angular官方明确不推荐这种用法,极易出现值不同步问题;二是提交按钮上绑定的onclick="location.href='#'"会触发页面跳转,可能打断正常的表单提交流程。
修复方案
核心原则
所有表单值操作优先使用Angular响应式表单提供的API,不要直接操作DOM修改输入框内容;移除游离在Angular组件外的原生全局JS函数,把扫码逻辑整合到组件内部。
具体修改步骤
- 移除原生全局的
decodeOnce函数,把zxing扫码逻辑移到Angular组件内,扫码成功后直接调用表单控件的setValue/patchValue方法更新值,Angular会自动把值同步到输入框视图,同时更新表单内部状态 - 移除
numRomaneio输入框上冗余的[(ngModel)]、name="name"属性,统一通过响应式表单API操作该字段值 - 删除提交按钮上的
onclick="location.href='#'"属性,避免不必要的页面跳转 - 增加组件销毁逻辑,扫码结束后及时释放摄像头资源
修正后的组件TS核心代码
// 按实际安装的zxing包路径导入依赖 import { BrowserMultiFormatReader } from '@zxing/library'; import { OnInit, AfterViewInit, OnDestroy } from '@angular/core'; export class QrcodeScanComponent implements OnInit, AfterViewInit, OnDestroy { formulario!: FormGroup; // 实例化二维码读取器 private codeReader = new BrowserMultiFormatReader(); romaneio: Romaneio = { id: 0, status: '' } idRomaneio!: number; numcarga!: number; id!: string; itens!: Item[]; displayedColumns = ['cod_fab', 'descricao', 'qac', 'qc', 'faltam'] constructor( private qrcodeapiService: QrcodeapiService, private fb: FormBuilder, private router: Router, private route: ActivatedRoute ) { } ngOnInit(): void { this.formulario = this.fb.group({ codEtiqueta: ['', Validators.required], numRomaneio: [0, Validators.required] }) this.romaneio.id = this.idRomaneio; // 初始化单号字段值,无需依赖ngModel this.formulario.get('numRomaneio')?.setValue(this.romaneio.id); } ngAfterViewInit(): void { // 视图渲染完成后启动扫码 this.startScan(); } // 整合原全局扫码逻辑到组件内部 private startScan() { // 默认调起设备后置摄像头,如需选择设备可提前枚举摄像头列表 this.codeReader.decodeFromVideoDevice(undefined, 'video', (result, err) => { if (result) { const scanText = result.getText(); console.log('扫码结果:', scanText); // 核心操作:直接更新表单控件值,禁止操作DOM改input value this.formulario.get('codEtiqueta')?.setValue(scanText); // 如需扫到结果后停止扫码可放开下一行注释 // this.codeReader.reset(); } if (err) console.error('扫码异常:', err); }) } buscarItens(){ this.qrcodeapiService.listar(this.romaneio.id).subscribe(itens =>{ this.itens = itens console.log(itens) }) } submit(){ if (this.formulario.invalid) return; const formValues = this.formulario.value; const etiqueta: Etiqueta = new Etiqueta(formValues.codEtiqueta, formValues.numRomaneio); this.qrcodeapiService.salvar(etiqueta).subscribe(() =>{ this.qrcodeapiService.showMessage('Registrado') // 重置表单统一使用响应式API this.formulario.patchValue({ codEtiqueta: '', numRomaneio: this.romaneio.id }) this.formulario.get('codEtiqueta')?.markAsUntouched(); this.buscarItens(); }); } ngOnDestroy(): void { // 组件销毁时释放摄像头资源 this.codeReader.reset(); } }
修正后的表单模板核心代码
<form [formGroup]="formulario" (ngSubmit)="submit()" action="#"> <div class="divEtiqueta"> <mat-form-field class="inputEtq"> <input formControlName="numRomaneio" matInput placeholder="Nº Romaneio" /> </mat-form-field> <mat-form-field class="inputEtq"> <input matInput id="codEtiqueta" formControlName="codEtiqueta" placeholder="Cód. Etiqueta" /> </mat-form-field> </div> <section class="container" id="demo-content"> <div> <video id="video" width="95%" height="300" style="border: 1px solid gray" ></video> </div> <div class="buttonConfirmar"> <button type="submit" class="btnConfirmar" id="confirmarButton" mat-raised-button >Confirmar </button> </div> </section> </form>
内容的提问来源于stack exchange,提问作者Guilherme Marques
相关产品推荐
相关产品推荐

