Search code examples
angularvalidationnestedformarray

How to adding validation in nested array with different FormGroup in angular


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". Here is stackblitz link

enter image description here


Solution

  • 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"