Home > other >  How to adding validation in nested array with different FormGroup in angular
How to adding validation in nested array with different FormGroup in angular

Time:06-23

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". enter image description here

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

  1. Use the event change in .html

    <select formControlName="device" 
           (change)="updateAppleForm.get('apple').updateValueAndValidity()">
    
  2. subscribe to valueChange of the FormControl. That's our function settypeListbecomes like

    settypeList(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"

  • Related