Cancelling file picker is causing stepper dialog to close

88 Views Asked by At

I am working on an Angular web app that collects data via a Nebular Stepper component. One of the steps allows users to attach images or document using another component that contains the type="file" input. Attaching an image works as expected but if you cancel the file picker, the underlying stepper dialog also closes and the data is lost. Am I correct to assume that this is being caused by the cancel event firing when closing the file picker? Is there a way to prevent that event from reaching the dialog?

Currently, if I attach a generic attachment and then delete it, cancelling the file picker does not close the stepper component.

I would like to prevent this behavior but am unsure how to accomplish it. I have attempted to stop the event propagation but all attemps have failed.

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: `
    <!--<span class="file-input-text">{{ labelText }}</span>-->
    <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>
    <!--<span class="file-input-text">{{ selectedFileText }}</span>-->
    <!--<button
      mat-icon-button
      (click)="filesChanged(null)"
      type="button"
      aria-label="Remove Selected File(s)"
      *ngIf="selectedFiles != null && selectedFiles.length > 0"
    >
      <mat-icon *ngIf="!customSvgIcon">close</mat-icon>
      <mat-icon *ngIf="customSvgIcon" [svgIcon]="customSvgIcon"></mat-icon>
    </button>-->
  `,
  styles: [
    ".file-input-button { margin-right: 8px !important }",
    // eslint-disable-next-line max-len
    ".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 = "";
  }
}

I confirmed that the file input is emitting a cancel event that is reaching my stepper component by adding an event listener to its constructor. Is there a way to make the stepper ignore that event?

1

There are 1 best solutions below

0
92awdGSX On

I was finally able to solve my problem by using @HostListener to listen for the cancel event and stopping its propagation on my upload component.

@HostListener('cancel', ['$event'])
  onCancel(event: Event){ 
    console.log('Cancel host listener.'); 
    event.stopImmediatePropagation(); 
  };