I'm having an issue with a custom validator that I've written. The validator is used to validate all the data in a component.
Here's the setup:
The component displays a table with X rows of data. Some of the rows are display-only mode and others will be in edit mode (certain cells in the table will have inputs, selects, etc). Backing the table is an array of data objects (tableData).
If a row is in edit mode, each input is two-way bound to the appropriate element in the tableData array.
My custom validator is applied to the form tag and takes tableData as input. Everything mostly works. The validation data looks at each row in the table and does everything I need it to do.
The issue is that the data based to my custom validator is old data. So if a row is in edit mode, when I change a value in a select, tableData is updated, but the version of it that's passed to the validator is before the update. So I'm always validating an old version of tableData.
I'm not sure how to get the validation to use the up to date version of tableData. I think the issue may be related to the fact that the select binding changes a value of an object in the tableData array, but the tableData array itself doesn't actually change.
I tried adding callback to the (change) event on the select in the row being edited. The method called on (change) manually triggers change detection using a ChangeDetectorRef, but that didn't work.
I don't want to spam everyone with all the entire files, so I've tried to just add the important snippets.
Here's the template:
<form #f="ngForm" novalidate custom-validator="{{tableData | json}}">
<p-dataTable [value]="tableData">
...
<p-column [header]="'Program Name'">
<template let-row="rowData" let-idx="rowIndex" pTemplate type="body">
<span *ngIf="!row['edit']">
{{row['data'].programName}}
</span>
<div *ngIf="row['edit']">
<select #progName="ngModel" [(ngModel)]="row['data'].programCode"
title="Select Program" required (change)="onProgramChange($event, idx)"
name="programSelect-{{idx}}">
<option [value]=""></option>
<option *ngFor="let prog of programList" [value]="prog.code">
{{prog.name}}
</option>
</select>
</div>
</template>
</p-column>
...
</p-dataTable>
</form>
Here's the backing component:
//imports...
...
private tableData: PersonAssignmentRowData[] = [];
private programList: Program[] = [];
...
onProgramChange(event: any, index: number) {
for(let prog of this.programList) {
if(prog.code == event.target.value) {
this.tableData[index].data.programAcronym = prog.acronym;
this.tableData[index].data.programLocation = prog.location;
this.tableData[index].data.programName = prog.name;
break;
}
}
}
...
Here's the validator:
@Directive({
selector: '[custom-validator]',
providers:[{provide: NG_VALIDATORS, useExisting: CustomValidator, multi: true}]
})
export class CustomValidator implements Validator{
@Input('custom-validator') tableDataString: string;
validate(control: AbstractControl) {
if(this.tableDataString == null || this.tableDataString.length == 0) {
return null;
}
let tableData: PersonAssignmentRowData[] = [];
tableData = JSON.parse(this.tableDataString);
let message: string = '';
//logic that tests the validity of the data and sets any error messages in the message variable
if(message.length > 0) {
return {'validationMessage': message};
} else {
return null;
}
}
}