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?
Solution 1:
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"/>
Solution 2:
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; }
}
Solution 3:
Input with type file
is not supported currently, see #7341