I have a nested array item list. I would like to add validation in "Apple Phone (If applicable)" (On the top) as required field if some items selected "Apple" in "Electronic device". The problem is how to control the validation in different form Group. Any one can help ?
Bellow image is what i want validation. I want to trigger a error message when any item selected "Apple"in "Electronic device".
CodePudding user response:
When you iterate over FormArray.controls and use a variable in the loop, e.g.
*ngFor="let sessionQuota of sessionFormArr.get('typeList')['controls']
The variable is an "AbstractControl", so you can use sessionQuota.errors and sessionQuota.touched
, e.g.
<div *ngIf="sessionQuota.errors && sessionQuota.touched">
error
</div>
BTW I prefer to have a function
getTypeList(index){
return this.sessionFormArr.at(index).get('typeList') as FormArray
}
and iterate over getTypeList(i).controls
*ngFor="let sessionQuota of getTypeList(i).controls"
CodePudding user response:
Before to create a custom validator, we need take account that Angular only check the validation is there're an input (or a custom formControl) and the user change the value or if manually we make an updateValueAndValidity.
So if we want Angular validate a control when we change another we can take two approach
Use the event change in .html
<select formControlName="device" (change)="updateAppleForm.get('apple').updateValueAndValidity()">
subscribe to valueChange of the FormControl. That's our function
settypeList
becomes likesettypeList(x) { let arr = new FormArray([]);
x.typeList.forEach((y) => { //first we create the group const group=this.fb.group( { subQuota: y.subQuota, device: y.device, } ) //subscribe to valueChanges group.get('device').valueChanges.pipe( takeUntil(this.active)) .subscribe(_=>{ setTimeout(()=>{ this.updateAppleForm.get('apple').updateValueAndValidity(); }) }) //and push the group arr.push(group); }); return arr; }
See that I use a "clasic" takeWhile to unsubscribe in onDestroy.
active: Subject<any> = new Subject<any>(); //we decalre a subject "active"
//and in ngOnDestroy emit a true and give as completed
ngOnDestroy() {
this.active.next(true);
this.active.complete();
}
The second election we need take is have two or only one FormGroup. If we have an unique FormGroup we need makes some changes in our code
We define our updateAppleForm
like
this.updateAppleForm = this.fb.group({
apple: [null,this.requiredIfApple],
sessionList: this.fb.array([])
});
And use a getter
//instead
//sessionListFormArr:FormArray
//use a getter
get sessionListFormArr(): FormArray {
return this.updateAppleForm.get('sessionList') as FormArray;
}
After in the .html we remove the <form [formGroup]="sessionListessionDynamicForm">
<div formArrayName="sessionList">
Well, our custom validator becomes like
requiredIfApple(control:AbstractControl)
{
const parent=control.parent;
if (!parent || control.value)
return null;
return parent.value.sessionList.find(x=>x.typeList.find(t=>t.device=='A'))?{inValidApple:"required"}:null
}
You can see in the stackblitz
But you ask about a validator that use variables of the components. For this you need use the javascript bind
You define your formGroup like
this.updateAppleForm = this.fb.group({
apple: [null,this.requiredIfApple().bind(this)]
});
And your custom error like
requiredIfApple()
{
return (control:AbstractControl)=>{
if (!this.sessionListessionDynamicForm || control.value)
return;
const form=this.sessionListessionDynamicForm.get('sessionList') as FormArray
return form.value.find(x=>
x.typeList.find(t=>t.device=='A'))?
{inValidApple:"required"}:null
}
}
Your forked stackblitz with this approach. See that we need make an updateValueAndValidity. In this stackblitz I use the approach of use the event "change"