Angular上传组件完成上传后不刷新无法再次上传问题求解
问题根因
- 文件选择器状态残留:原生
<input type="file">选择文件后会保留当前value值,后续如果选择相同文件不会触发(change)事件,部分场景下也会导致交互异常 - Socket事件重复注册:每次调用
upload方法都会新增4个socket事件回调,旧回调没有被销毁,多次上传后同个事件会触发多次回调,导致状态更新混乱,甚至阻塞变更检测,表现为操作无响应 - 状态未全量重置:当前只在上传前重置了上传进度和提示信息,导入类型、处理计数等状态没有重置,会导致后续上传逻辑异常
修复方案
第一步:修改模板代码
给文件输入框添加模板引用,方便后续重置状态:
<input #fileInput type="file" style="display: none;" name="file" id="file" accept=".csv" (change)="upload($event)"/>
第二步:修改组件代码
新增状态清理逻辑,避免事件重复注册,全量重置状态:
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, NgZone } from '@angular/core'; import { HttpEvent, HttpEventType } from '@angular/common/http'; // 其他原有导入保持不变 export class UploadComponent implements OnInit, OnDestroy { importTypeId; message = null; uploadedPercentage = 0; importTypeOptions$: Observable<Array<IServerDropdownOption>>; processedRecord = 0; totalRecord = 0; // 存储socket事件清理函数 private socketCleanups: Array<() => void> = []; // 引用文件输入框 @ViewChild('fileInput') fileInput: ElementRef<HTMLInputElement>; constructor( private router: Router, private uploadService: UploadService, private cbLookupService: CouchbaseLookupService, private socketService: SocketService, private ngZone: NgZone // 解决socket回调不在NgZone内导致的视图不更新问题 ) { this.importTypeOptions$ = this.cbLookupService.getOptions(DropdownGuids.IMPORT_TYPE); } ngOnInit() { } ngOnDestroy() { // 组件销毁时清理所有socket监听 this.clearSocketListeners(); } // 清理socket监听器的公共方法 private clearSocketListeners() { this.socketCleanups.forEach(cleanup => cleanup()); this.socketCleanups = []; } upload($event) { // 先清理上次上传的socket监听 this.clearSocketListeners(); // 全量重置所有状态 this.uploadedPercentage = 0; this.message = null; this.processedRecord = 0; this.totalRecord = 0; const file = $event.target.files[0]; // 注册socket事件时保存清理函数,通常ioInject会返回取消监听的方法 this.socketCleanups.push( this.socketService.ioInject('recCount', (count) => { this.ngZone.run(() => { this.message = `总记录数:${count}`; this.totalRecord = count; }); }), this.socketService.ioInject('recProcessed', (range) => { this.ngZone.run(() => { this.message = `${file.name} 导入中..`; this.processedRecord = Number(range.rowcount); this.totalRecord = Number(range.recCount); }); }), this.socketService.ioInject('recSaved', (payload) => { this.ngZone.run(() => { this.message = `已处理 ${payload.data} 条,总计 ${payload.recordcount} 条`; this.processedRecord = payload.data; }); }), this.socketService.ioInject('recError', (err) => { this.ngZone.run(() => { this.message = `处理出错,请重试。错误信息:${err}`; this.uploadedPercentage = 0; // 出错时重置文件选择器 this.fileInput.nativeElement.value = ''; }); }) ); this.uploadService.uploadCsv(file, this.importTypeId).subscribe((event: HttpEvent<any>) => { switch (event.type) { case HttpEventType.Sent: this.message = `${file.name} 上传中..`; break; case HttpEventType.Response: this.message = event.body.data; // 上传完成后重置文件选择器,可按需选择是否重置importTypeId this.fileInput.nativeElement.value = ''; // this.importTypeId = null; break; case 1: { if (Math.round(this.uploadedPercentage) !== Math.round(event['loaded'] / event['total'] * 100)) { this.uploadedPercentage = event['loaded'] / event['total'] * 100; } break; } } }, error => { console.log(error); this.message = '上传出错,请重试'; this.uploadedPercentage = 0; // 出错时重置文件选择器 this.fileInput.nativeElement.value = ''; }); } }
修复说明
- 每次上传前先清理上次注册的socket事件,避免重复监听导致的状态混乱
- 上传完成/出错后手动重置文件输入框的value值,确保下次选择文件一定会触发change事件
- 注入NgZone确保socket回调在Angular变更检测区域内执行,避免视图不更新的问题
- 新增ngOnDestroy生命周期钩子,组件销毁时清理残留的socket监听,避免内存泄漏
内容的提问来源于stack exchange,提问作者SamS87
相关产品推荐
相关产品推荐

