Angular中取消文件选择器致Nebular Stepper对话框关闭的解决问询
Angular Nebular Stepper对话框在取消文件选择器时意外关闭
问题背景
使用Angular + Nebular Stepper组件开发数据收集页面,其中一个步骤通过自定义mat-file-upload组件实现PDF上传功能:
- 文件上传功能正常,但取消系统文件选择器时,整个步进器对话框会被意外关闭,导致已填写数据丢失
- 特殊场景:先添加附件再删除后,取消文件选择器不会触发对话框关闭
- 已通过事件监听确认:文件输入框会触发
cancel事件并向上冒泡到步进器组件 - 尝试过阻止事件传播,但未生效
相关代码
stepper.component.ts
/** * Handler for the selectedFilesChanged event of mat-file-upload component * for Attachments. * @param {FileList} fileList List of File objects of the loaded files. */ onSelectedAttachmentsChanged(fileList: FileList): void { // White list for attachments - just PDF for now. if (fileList.item(0).type == "application/pdf") { getBase64EncodedFromFile(fileList.item(0)).subscribe({ next: (result: string) => { const newAttachment: Attachment = { filename: fileList.item(0).name, fileExtension: FileExtension.pdf, data: result, mimeType: fileList.item(0).type, }; this.changeRequest.attachments = [ ...this.changeRequest.attachments, newAttachment, ]; }, error: (err: Error) => { console.log("An error has occurred: ", err); }, }); } else { this._dialogService.open(ConfirmDialogComponent, { context: { confirmHeader: "Unsupported File Type", confirmMessage: "Only PDFs can be attached here.", confirmBtnText: "Ok", confirmBtnStatus: "basic", confirmStatus: "info", confirmShowCancelBtn: false, }, }); } }
stepper.component.html
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="1em"> <mat-file-upload [acceptedTypes]="'.pdf'" [labelText]="''" [selectButtonText]="'Attach a PDF'" [showUploadButton]="false" [allowMultipleFiles]="false" (selectedFilesChanged)="onSelectedAttachmentsChanged($event)" ></mat-file-upload> </div>
mat-file-upload.component.ts
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild, } from "@angular/core"; @Component({ selector: "mat-file-upload", template: ` <button nbButton [type]="selectFilesButtonType" [disabled]="disabled" color="primary" class="file-input-button" (click)="fileInput.click()" [attr.aria-label]="selectButtonText" > <nb-icon icon="plus-outline"></nb-icon> <span>{{ selectButtonText }}</span> <input #fileInput type="file" style="display: none" [accept]="acceptedTypes" [multiple]="allowMultipleFiles" (change)="filesChanged($event.target.files)" /> </button> <button mat-raised-button [type]="uploadButtonType" color="primary" class="file-input-button" [disabled]="!selectedFiles || disabled" (click)="uploadFiles()" *ngIf="showUploadButton" [attr.aria-label]="uploadButtonText" > {{ uploadButtonText }} </button> `, styles: [ ".file-input-button { margin-right: 8px !important }", ".file-input-text { font-size: 14px !important; margin-right: 8px !important }", ], }) export class MatFileUploadComponent { @Input() labelText = "Select File(s)"; @Input() selectButtonText = "Select File(s)"; @Input() selectFilesButtonType: "button" | "menu" | "reset" | "submit" = "button"; @Input() uploadButtonText = "Upload File(s)"; @Input() uploadButtonType: "button" | "menu" | "reset" | "submit" = "button"; @Input() allowMultipleFiles = false; @Input() showUploadButton = true; @Input() acceptedTypes = "*.*"; @Input() customSvgIcon?: string = null; @Input() disabled = false; @Output() uploadClicked: EventEmitter<FileList> = new EventEmitter<FileList>(); @Output() selectedFilesChanged: EventEmitter<FileList> = new EventEmitter<FileList>(); @ViewChild("fileInput") fileInputRef: ElementRef; selectedFiles: FileList; selectedFileText = ""; filesChanged(files?: FileList): void { this.selectedFiles = files; this.selectedFilesChanged.emit(this.selectedFiles); if (this.selectedFiles) { const numSelectedFiles = this.selectedFiles.length; this.selectedFileText = numSelectedFiles === 1 ? this.selectedFiles[0].name : `${numSelectedFiles} files selected`; } else { this.selectedFileText = ""; this.resetFileInput(); } } uploadFiles(): void { this.uploadClicked.emit(this.selectedFiles); this.resetFileInput(); } resetFileInput(): void { this.fileInputRef.nativeElement.value = ""; } }
解决方案
1. 直接捕获并阻止文件输入框的cancel事件
修改mat-file-upload.component.ts的模板,给文件输入框添加cancel事件处理器,阻止事件冒泡:
<input #fileInput type="file" style="display: none" [accept]="acceptedTypes" [multiple]="allowMultipleFiles" (change)="filesChanged($event.target.files)" (cancel)="onFileInputCancel($event)" <!-- 新增事件绑定 --> />
在组件类中添加事件处理方法:
onFileInputCancel(event: Event): void { event.stopPropagation(); // 阻止事件向上传递到步进器对话框 event.preventDefault(); // 可选:取消默认行为 }
2. 阻止上传按钮的点击事件冒泡
修改mat-file-upload组件中的触发按钮,确保点击事件不会向上传递:
<button nbButton [type]="selectFilesButtonType" [disabled]="disabled" color="primary" class="file-input-button" (click)="fileInput.click(); $event.stopPropagation()" <!-- 新增阻止冒泡 --> [attr.aria-label]="selectButtonText" >
3. 优化filesChanged方法的逻辑
取消文件选择器时,files参数为undefined,此时不需要触发selectedFilesChanged事件,避免触发意外的对话框关闭逻辑:
filesChanged(files?: FileList): void { this.selectedFiles = files; // 仅当有文件选中时才触发事件 if (files) { this.selectedFilesChanged.emit(this.selectedFiles); const numSelectedFiles = this.selectedFiles.length; this.selectedFileText = numSelectedFiles === 1 ? this.selectedFiles[0].name : `${numSelectedFiles} files selected`; } else { this.selectedFileText = ""; this.resetFileInput(); } }
4. 可选:添加步进器对话框的关闭拦截
如果以上方法仍未解决问题,可以在步进器所在的对话框组件中添加关闭拦截,仅允许用户主动关闭:
import { NbDialogRef } from '@nebular/theme'; constructor(private dialogRef: NbDialogRef<YourStepperDialogComponent>) { // 拦截对话框关闭请求 this.dialogRef.onBeforeClose().subscribe(() => { // 这里可以添加判断逻辑,比如检查是否是文件选择器触发的关闭 // 如果是意外触发,返回false阻止关闭 // 示例:仅当用户点击确认按钮时允许关闭 return this.isUserInitiatedClose; }); }
内容的提问来源于stack exchange,提问作者92awdGSX
相关产品推荐
相关产品推荐

