You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 10:27:03