Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2: validation for <input type="file"/> won't trigger when changing the file to upload

Angular 2 seems to have troubles with running validation when a file input changes.

I made a plunk to illustrate this problem:

I make a formGroup like

this.frm = new FormGroup({
    file: new FormControl("", this.validateFile)
});

And in the validateFile function I throw an alert and log to the console:

public validateFile(formControl: FormControl): {[key: string]: any; } {
   alert('Validation ran');
   console.log('Validation ran');
}

Plunkr to illustrate the issue: https://plnkr.co/edit/Pgcg4IkejgaH5YgbY3Ar?p=preview

The validation will run when initializing the page but won't run each time you change the file to be uploaded.

Is there any solution to this problem?

like image 233
S. Robijns Avatar asked Jan 27 '17 08:01

S. Robijns


3 Answers

I fixed it using kemsky answer and Sebastien's comment. I made a ngValueAccessor which registers itself on every input with type file.

Plunkr can be found here.

Most relevant code + explanation beneath:

This adds a ControlValueAccessor for file inputs which might be part of the angular framework itself someday(#7341). A file input works different than other controls. This piece of code makes sure the selected files are read as the value:

import {Directive} from "@angular/core";
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from "@angular/forms";

@Directive({
    selector: "input[type=file]",
    host : {
        "(change)" : "onChange($event.target.files)",
        "(blur)": "onTouched()"
    },
    providers: [
        { provide: NG_VALUE_ACCESSOR, useExisting: FileValueAccessor, multi: true }
    ]
})
export class FileValueAccessor implements ControlValueAccessor {
    value: any;
    onChange = (_) => {};
    onTouched = () => {};

    writeValue(value) {}
    registerOnChange(fn: any) { this.onChange = fn; }
    registerOnTouched(fn: any) { this.onTouched = fn; }
}

And for the 'required' validation I made a validator which I use by adding the static validate method to the file FormControl for ReactiveForms. (or as a directive for template driven forms).

import {Directive} from "@angular/core";
import {NG_VALIDATORS, Validator, FormControl} from "@angular/forms";

@Directive({
    selector: "[requiredFile]",
    providers: [
        { provide: NG_VALIDATORS, useExisting: FileValidator, multi: true },
    ]
})
export class FileValidator implements Validator {
    static validate(c: FormControl): {[key: string]: any} {
        return c.value == null || c.value.length == 0 ? { "required" : true} : null;
    }

    validate(c: FormControl): {[key: string]: any} {
        return FileValidator.validate(c);
    }
}

Building my form looks like this:

private buildForm() {
    this.frm = new FormGroup({
        file: new FormControl("",    [FileValidator.validate])
    });
}

And for the html:

<input type="file" formControlName="file"/>
like image 155
S. Robijns Avatar answered Oct 11 '22 04:10

S. Robijns


Angular 4+ has changed there hostbindings.

import { Directive, HostListener } from "@angular/core";
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from "@angular/forms";

@Directive({
    selector: "input[type=file]",
    providers: [
        {provide: NG_VALUE_ACCESSOR, useExisting: FileValueAccessorDirective, multi: true}
    ]
})
export class FileValueAccessorDirective implements ControlValueAccessor {
    @HostListener('change', ['$event.target.files']) onChange = (_) => {};
    @HostListener('blur') onTouched = () => {};

    writeValue(value) {}
    registerOnChange(fn: any) { this.onChange = fn; }
    registerOnTouched(fn: any) { this.onTouched = fn; }
}
like image 45
Fritz Avatar answered Oct 11 '22 03:10

Fritz


Input with type file is not supported currently, see #7341

like image 2
kemsky Avatar answered Oct 11 '22 04:10

kemsky