PierBJX
PierBJX

Reputation: 2353

Update parent when variable in child change - Angular

I have a parent which is a form. This form is composed of two child components:

experiment create (parent)

I use a angular component -> mat-accordion to navigate through the two children components. I use @Input to have the result of what is filled in the children component into the parent. I want to submit the form only if a file is chosen for both of them. Therefore, I set a variable (in datasetList[i].fileValid) to say whether a file has been selected. Like this I disabled the button if a file is not updated. To disable the button I called the two function:

However, when the variable changed for the second child component it does not updated the disabled button. This works, only if I press "previous" and "next". The button is not disabled anymore. Like if I needed to reload or refresh the parent. Maybe because of the life cycle ?

Parent Component:

export class ExperimentCreateComponent implements OnInit {
  data: any = {};
  datasetList: any = [{ fileValid: false }];
  metadataList: any = [{ fileValid: false }];

  // Functions to navigate through the expansion panels
  setStep(index: number) {
    this.step = index;
  }

  nextStep() {
    this.step++;
  }

  prevStep() {
    this.step--;
  }

  isDatasetFilesValid() {
    return this.datasetList.findIndex(function(item, i) {
      return item.fileValid == false;
    });
  }

  isMetadataFilesValid() {
    return this.metadataList.findIndex(function(item, i) {
      return item.fileValid == false;
    });
  }
}

Parent HTML:

<div class="jumbotron">
  <div class="container">
    <div class="row">
      <div class="col-sm-8 offset-sm-2">

        <form name="form" (ngSubmit)="f.form.valid" #f="ngForm" novalidate>

          <mat-accordion class="headers-align">

            <mat-expansion-panel id="datasetUpload" [expanded]="step === 0" (opened)="setStep(1)" hideToggle="true">

              <app-creation-dataset [datasetList]="datasetList"></app-creation-dataset>

              <mat-action-row>
                <button mat-button color="warn" (click)="prevStep()">Previous</button>
                <button mat-button color="primary" (click)="nextStep()">Next</button>
              </mat-action-row>
            </mat-expansion-panel>

            <mat-expansion-panel id="metadataUpload" [expanded]="step === 1" (opened)="setStep(2)" hideToggle="true">

              <app-creation-metadata [metadataList]="metadataList"></app-creation-metadata>

              <mat-action-row>
                <button mat-button color="warn" (click)="prevStep()">Previous</button>
                <button mat-button color="primary" type="submit" [disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)" (click)="createExperiment()">End</button>
              </mat-action-row>
            </mat-expansion-panel>

          </mat-accordion>

        </form>
      </div>
    </div>
  </div>
</div>

Child Component:

export class CreationDatasetComponent implements OnInit {
  @Input() datasetList: any = [{ fileValid: false }];
  fileSelected: File;


  constructor(private papa: Papa, private cd: ChangeDetectorRef) {}

  ngOnInit() {}

  onChange(files: FileList, index: number, dom: any) {
    // Option to parse the file with papaparse
    let options = {
      header: true,
      error: (err, file) => {
        this.datasetList[index].fileValid = false;
        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].headers = results.meta.fields;
        this.datasetList[index].values = results.data;
        this.datasetList[index].filename = filename;
        this.datasetList[index].is_metadata = false;
        this.datasetList[index].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({ fileValid: false });
  }

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

Child HTML:

<div *ngFor="let dataset of datasetList; let index = index">
  <div id="datasetFiles">
    <h6>Select the type of dataset and 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>
    </div>
  </div>
</div>
<div>
  <button mat-icon-button color="primary" (click)="addDataset()">
    <mat-icon>add_box</mat-icon>
  </button>
</div>

Upvotes: 6

Views: 15580

Answers (2)

Luca Taccagni
Luca Taccagni

Reputation: 1059

So, to make this answer more clear, read comments on question.

I'm going to past the example for the @Output:

this is the CHILD.COMPONENT.TS

@Component({
  selector: 'children',
  templateUrl: './children.component.html',
  styleUrls: ['./children.component.scss'],
  providers: [{...
  })
})

export class ChildrenComponent {

  @Output() editedEmitter = new EventEmitter<number>();
  
  private variableToPass = 10;
  
  constructor() {}
  
  functionToCall() {
    this.editedEmitter.emit(20);
  }

this is the PARENT.COMPONENT.HTML

<section>
      <children (editedEmitter)="updateValue($event)"></children>
</section>

<!-- in the component you'll do 
  updateValue(val: number) {
    this.variableToUpdate = val;
  }
-->

Upvotes: 9

Gijs Post
Gijs Post

Reputation: 199

[disabled] requires a condition (true or false), the code you put in: isMetadataFilesValid() != -1 isDatasetFilesValid() != -1 is not a proper condition, they are two. If you want both conditions to be true, use the && notation.

[disabled]="(isMetadataFilesValid() != -1) && (isDatasetFilesValid() != -1)"

Alternatively I suggest moving the condition to the functions themselves so they return a boolean.

Upvotes: 1

Related Questions