Search code examples
angulartypescriptangular-materialangular-forms

How to validate duplicates for dynamically added form field


I have a from called verification form and a form array called RepDetails with three fields. By default the form shows three fields. User can add more details by clicking on add more. Now I want to check Mydetails[0].name should not match with mydetails[1].name. Can anyone help on this

Myform = this.fb.group({   
    Mydetails: this.fb.array([this.createMydetails()])   
  });

createMydetails(): FormGroup {
    return this.fb.group({
      Myname: ['', Validators.required ],
      Myid: ['', Validators.required],
      Myphone: ['', Validators.required]
    });
  }

Html

<div
        formArrayName="Mydetails"
        *ngFor="let item of Mydetails.controls; let i = index"
      >
        <div [formGroupName]="i">
          <mat-form-field appearance="outline">
            <mat-label class="required">
              Name
            </mat-label>
            <input
              appAlphaNumeric
              [maxlength]="maxRepNamelen"
              formControlName="Myname"
              class="mat-body-1"
              matInput
            />
          </mat-form-field>
          <mat-form-field appearance="outline">
            <mat-label class="required">
              Id
            </mat-label>
            <input
              appAlphaNumeric
             
              formControlName="Myid"
              class="mat-body-1"
              matInput
            />
          </mat-form-field>
          <div class="phn-wrapper">
            <ngx-intl-tel-input
              [cssClass]="'int-phn'"
              [searchCountryField]="[
                SearchCountryField.Iso2,
                SearchCountryField.Name
              ]"name="phone"
              formControlName="Myphone"
            >
            </ngx-intl-tel-input>
          </div>
          <mat-icon
            *ngIf="Mydetails.length > 1"
            (click)="remove(i)"
            class="close"
            >close</mat-icon
          >
          
        </div>
      </div>
        class="add-fields"
        (click)="add()"
      >
        <mat-icon class="icon">add_circle_outline</mat-icon>
        <span class="text mat-button">
          {{"ADD MORE"}}</span
        >
      </div>

Solution

  • In this SO you has a custom validation over a FormArray. The problem if you use a custom validation over a formArray is that is checked always you change any value in the formArray (any change in repName, or in passport or in phoneName)

    You can create a custom validator over a Form Control that take account others controls

      checkIfUnique(index) {
        return (control: FormControl) => {
          //try get the form array
          //control.parent is the FormGroup, control.parent.parent is the formArray
          const formArray =
            control.parent && control.parent.parent
              ? (control.parent.parent as FormArray)
              : null;
          if (formArray && formArray.controls.length) {
            for (let i = index - 1; i >= 0; i--) {
              if (
                (formArray.at(i) as FormGroup).get("repName").value == control.value
              )
                return { errorRepeat: true };
            }
          }
        };
      }
    

    You need, when make the formGroup of the formArray, pass to the validator the "index" of element. So your need change your function createRep

    //pass the index
    createRep(index:number): FormGroup {
        return this.fb.group({
          repName: ['', [Validators.required,this.checkIfUnique(index) ]],
          passport: ['', Validators.required],
          phoneNumber: ['', Validators.required]
        });
      }
    

    Well, the last we need is, when change a value of any 'repName', check the rest of controls. Remember that Angular check the formControl that you change, but not the rest, so if repName[0]="a" and repName[1]="b", when change repName[0] to "b" Angular don't check repName[1]. So we create a function

      checkFormArray()
      {
        this.detailsFormArray.controls.forEach(x=>{
          (x as FormGroup).get('repName').updateValueAndValidity()
        })
      }
      //I use a getter of the formArray
      get detailsFormArray() {
         return (this.verificationForm.get("repDetails") as FormArray)
      }
    

    And in the input we call to the function

      <input formControlName="repName" (input)="checkFormArray()">
    

    You can see the stackblitz

    NOTE: I remove the tag angularjs from your question (your question is only about angular)