Your FormArray consists of FormGroups, allowing you to inspect a single FormControl within the FormGroup.
(If your FormArray were made up of FormControls, you would need to implement validators on the "FormArray" itself.)
By applying a validator to a control, we gain access to its parent and grandparent elements. In this scenario, the component's parent is the formGroup and the parent of the parent is the formArray.
The challenge arises when adding a validator to a FormControl that relies on others; in such cases, we must ensure that the control is also validated whenever related controls are modified.
I will attempt to clarify through comments, so please make sure to comprehend the logic instead of merely copying the code.
uniqueLabelsValidator() {
return (control: AbstractControl) => {
//only validate if the control has a value
if (control.value) {
//retrieve the formGroup
const group = control?.parent as AbstractControl;
//obtain the FormArray
const array = control?.parent?.parent as FormArray;
if (array) {
//determine the index of the formGroup
//e.g., when modifying the second "label,"
//the group corresponds to the second formGroup (index=1)
const index = array.controls.findIndex(
(x: AbstractControl) => x == group
);
//update and validate all subsequent "labels" after the specified index
//in the given example, it refers to labels in the third or fourth position
setTimeout(()=>{
array.controls.forEach((group:AbstractControl,i:number)=>{
i>index && group.get('label')?.updateValueAndValidity()
})
})
//create an array containing the values of the labels
const values = array.value.map((group: any) => group.label);
//check for any duplicate values before the changed "label"
if (
values.find((x: any, i: number) => x == control.value && i < index)
)
return { duplicate: true };
}
}
return null;
};
}
You can explore this functionality further in the StackBlitz demo.
UPDATE: The previous validator only marks the "duplicate" as an error, not both. To flag both as duplicates, replace i<index
and i>index
with i!=index
.
uniqueLabelsValidator() {
return (control: AbstractControl) => {
...
setTimeout(()=>{
array.controls.forEach((group:AbstractControl,i:number)=>{
i!=index && group.get('label')?.updateValueAndValidity()
})
})
...
if (
values.find((x: any, i: number) => x == control.value && i != index)
)
return { duplicate: true };
}
}
...
};
}