Search code examples
javascripthtmlangularrequiredangular-require

Angular input file required


I have a div which contains one or more inputs files according to a list. This is generated with ngFor. I remove or add element of the list with two buttons and of course it updates the HTML.

Here is the html:

 <form name="form" (ngSubmit)="f.form.valid" #f="ngForm" novalidate>
    <div *ngFor="let dataset of datasetList; let index = index">

      <div id="datasetFiles">
        <h6>Browse the files:</h6>
        <div class="container">
          <div class="row justify-content-between">
            <div class="col-6 d-flex align-items-center">
              <input id="file" #file (change)="onChange(file.files, index, $event.currentTarget)" type="file">
            </div>
            <div class="col-2 d-flex align-items-center">
              <button mat-icon-button color="warn (click)="removeDataset(index)">
                <mat-icon>delete</mat-icon>
              </button>
            </div>
          </div>
        </div>
        <div *ngIf="typeDataset.invalid && (typeDataset.dirty || typeDataset.touched)" class="alert alert-danger">
          Dataset type is required
        </div>
        <div *ngIf="!fileValid" class="alert alert-danger">
          Dataset file required
        </div>
      </div>
    </div>
    <div>
      <button mat-icon-button color="primary" (click)="addDataset()">
        <mat-icon>add_box</mat-icon>
      </button>
    </div>

    <button mat-button color="primary" type="submit" [disabled]="!f.form.valid && !fileValid" (click)="createExperiment()">Submit</button>
</form>

Then in my component:

  onChange(files: FileList, index: number, dom: any) {
    this.fileValid = false;

    // Option to parse the file with papaparse
    let options = {
      error: (err, file) => {
        alert(
          "Unable to parse CSV file, please verify the file can be accessed and try again. Error reason was: " +
            err.code
        );
        return;
      },
      complete: (results, file) => {
        console.log("Parsed:", results, file);
        let filename = file.name;

        // Add the dataset to the datasetList
        this.datasetList[index].values = results.data;
        this.fileValid = true;
        this.cd.detectChanges();
      }
    };
    this.fileSelected = files[0]; // Get the file
    // Call the function to parse the file, option is the callback
    this.papa.parse(this.fileSelected, options);
  }

  // Add a dataset form
  addDataset() {
    this.datasetList.push({});
  }

  // Remove a dataset form
  removeDataset(index: number) {
    this.datasetList.splice(index, 1);
  }

This works properly. I use papaparse to read the file and add it to the datasetList. However, my problem was that I was not able to add "required" to the input file. But with what I have read, it seems that it does not exist.

Therefore, I add a variable "fileValid" to check if the file is properly selected or not. This variable is initialised to false and changed to true if the file is properly parsed. Then, if the user adds a file this variable changes to false. However, I don't know how to manage when a user deletes a div. For example:

  • if I have 3 div for the input file and two of them don't have file selected. The user can delete one of those and the variable "fileValid" should be still false.
  • if I have 2 div for the input file and one of them is selected and the other one not selected. If the user deletes the not selected one, the variable "fileValid" should be true.

How could I manage it ? Or Is there another way ?


Solution

  • Instead of trying to manage an entire list's validaty with only a single local boolean, you should consider increasing the dimension of your local boolean by turning it into an array of booleans. In this array, each index would represent the file validaty of the associated dataset at position index in datasetList.

    Change:

    fileValid: boolean = false;
    

    to:

    fileValid: Array<boolean> = [false]; // ideally the length of this would be equal to datasetList, but you should be able to get away by not bothering to initialize to that end.
    

    Then in your onChange method:

    this.fileValid[index] = false; // since you already conveniently have the index
    

    or

    this.fileValid[index] = true;
    

    and in your template for the [disabled] part, you can have something like "! isFileValid()"

    where in your component it would be:

    isFileValid(): boolean {
      return this.fileValid.indexOf(false) == -1
    }
    

    Hope it helps.