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

3

20

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?

Durative answered 27/1, 2017 at 8:9 Comment(0)
D
47

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"/>
Durative answered 30/1, 2017 at 14:36 Comment(4)
Hi,thanks for your help . I try to run this on angular 4.3 but it's doesn't work. First, it's still invalid after i upload file and second my 'file' field after submit it's just empty string.. I need to change something ? Thank you again,Schwenk
@S.Robijns I don't have plunker to show , just nothing happen. is you success to get file object on your onSubmit() function ? Thx .Schwenk
@S.Robijns this is not working in Angular 5+ here is example angular-input-flie-validation-reactive-formMigraine
No need for custom required validation. You can just use Angular's Validators.required validator which uses this function under the hood: github.com/angular/angular/blob/….Mcloughlin
S
10

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; }
}
Sanious answered 23/10, 2017 at 13:30 Comment(4)
It seems it did not change in angular 4, it looks like HostBinding en HostListener have always been around. Both ways work but according the style guide this one is indeed preferred(angular.io/guide/styleguide)Durative
Just ran into a case that proves it's definitely better to use host bindings, since they can be inherited and when you put it in the decorator it can't. More here: medium.com/@ttemplier/… I'll probably modify my answer sometime laterDurative
@MrBoJangles yeah idd.. don't count on it anymore, time is of the essence my friend :)Durative
@Sanious this is not working in Angular 5+ here is example angular-input-flie-validation-reactive-formMigraine
R
2

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

Risibility answered 27/1, 2017 at 8:17 Comment(1)
It is not supported but you can make it work. In the linked discussion there is an example of how to do it.Almeta

© 2022 - 2024 — McMap. All rights reserved.