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>
The @Output() decorator in a child component or directive lets data flow from the child to the parent. @Output() marks a property in a child component as a doorway through which data can travel from the child to the parent.
To update the parent state from the children component, either we can use additional dependencies like Redux or we can use this simple method of passing the state of the parent to the children and handling it accordingly.
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;
}
-->
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With