Pass a DOM event to custom form validator in Angular

Pass a DOM event to custom form validator in Angular

Problem Description:

I am trying to validate a form using the reactive approach. I am using the file input to take a file from the user. I have defined a custom validator that allows the user to upload a file on certain conditions. While trying to do so, I am getting an error. The validator does not receive the event as a whole but rather only the path of the file something like C:fakepathabc.xlsx. I want to pass the DOM event so that I can handle all the properties of files like type, size etc.

Here’s my code:

file.validator.ts

import { AbstractControl } from '@angular/forms';

export function ValidateFile(control: AbstractControl) : 
{ [key: string]: boolean } | null {
    const value = control.value;

    if (!value) {
        return null;
    }

    return value.length < 0 && value.files[0].type !== '.xlsx' && value.files[0].size > 5000000
    ? { invalidFile: true } : null;

}

sheet.component.ts

constructor(
private formBuilder: FormBuilder,
private alertService: AlertService
) {
    this.sheetForm = this.formBuilder.group({
    sheetType: ['Select Sheet Type', [Validators.required]],
    sheetUpload: [null, [Validators.required, ValidateFile]],
    sheetDescription: [
      null,
      [
        Validators.required,
        Validators.minLength(10),
        Validators.maxLength(100),
      ],
    ],
  });
}

sheet.component.html

<div class="input-group">
    <label for="sheet-upload">Upload Sheet: </label> &nbsp; &nbsp;
    <input
      id="sheet-upload"
      type="file"
      (change)="handleFileInput($event)"
      formControlName="sheetUpload"
      accept=".xlsx"
    />
    <small
      id="custom-error-message"
      *ngIf="
        (sheetForm.get('sheetUpload').dirty ||
          sheetForm.get('sheetUpload').touched) &&
        sheetForm.get('sheetUpload').invalid
      "
    >
      The file size exceeds 5 MB or isn't a valid excel type. Please
      upload again.
    </small>
</div>

Any help would be appreciated. Thanks!

Solution – 1

Not sure if this is the best way but it works

  • Create a directive to attach the native element to form control
  • On validation get the file from the native element in the validator
  • And also to use formControlName you need to assign a formGroup in the parent element (ignore if included in some other parent element)
@Directive({
  selector: '[formControlName]',
})
export class NativeElementInjectorDirective implements OnInit {
  constructor(private el: ElementRef, private control: NgControl) {}

  ngOnInit() {
    (this.control.control as any).nativeElement = this.el.nativeElement;
  }
}

file.validator.ts

export function ValidateFile(control: any): { [key: string]: boolean } | null {
  const value = control.value;
  const file = control?.nativeElement?.files[0];

  if (!value) {
    return null;
  }

  return value.length < 0 || file.type !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.size > 5000000
    ? { invalidFile: true }
    : null;
}

sheet.component.html

<div class="input-group" [formGroup]="sheetForm">
  <label for="sheet-upload">Upload Sheet: </label> &nbsp; &nbsp;
  <input
    id="sheet-upload"
    type="file"
    formControlName="sheetUpload"
    accept=".xlsx"
  />
  <small
    id="custom-error-message"
    *ngIf="
      (sheetForm.get('sheetUpload').dirty ||
        sheetForm.get('sheetUpload').touched) &&
      sheetForm.get('sheetUpload').invalid
    "
  >
    The file size exceeds 5 MB or isn't a valid excel type. Please upload again.
  </small>
</div>

Solution – 2

You can get reference to input element and use it in validator.

<input #sheetUpload ...>

@ViewChild('sheetUpload') fileInput: HTMLInputElement;

private ValidateFile(): ValidatorFn {
return (control) => {
  const value = control.value;

  if (!value || !this.fileInput) {
    return null;
  }

  const file = this.fileInput.files[0];

  return value.length < 0 && file.type !== '.xlsx' && file.size > 5000000
    ? { invalidFile: file.name }
    : null;
  }
}
Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject