Check if there is a duplicate item in FormGroups field in Angular reactive form

Viewed 473

While saving all my grid rows, I need to check whether there is a duplicate for a specific column field

 (this.formGroups.get('items') as FormArray).controls.forEach((item) => {
    console.log(item.value.attributeDisplayName);       
  });

I need to stop the form from submission by showing an alert or something similar when there is a duplicate value for attributeDisplayName. How can I check that in the current forEach loop. Thanks

I am using kendo angular components in my HTML markup and I am not using form tag. Below is an example for column declaration

  <kendo-grid-column field="attributeDisplayName" title="CUSTOM FIELD LABEL" width="190">  
        <ng-template kendoGridEditTemplate let-column="column" let-formGroup="formGroup" >          
          <input ngDefaultControl  class="k-textbox" [formControl]="formGroup.get(column.field)">
          
          <div *ngIf="formGroup.get(column.field).errors && (formGroup.get(column.field).dirty || formGroup.get(column.field).touched)">
                <span style="color:red" class="k-icon k-i-warning"></span>
                <span style="color:red">CUSTOM FIELD LABEL is a required field</span>
                
          </div>

        </ng-template>
    </kendo-grid-column>

Below is FormGroup declartion in .ts file

    public formGroup: FormGroup;
    public formGroups: FormGroup = new FormGroup({ items: new FormArray([]) });
    
    public createFormGroup = (dataItem) =>
        new FormGroup({
            atrributeId: new FormControl(dataItem.atrributeId),
            objectType: new FormControl(dataItem.objectType, Validators.required),
            attributeDisplayName: new FormControl(dataItem.attributeDisplayName, Validators.required),     
            dataType: new FormControl(dataItem.dataType, Validators.required),
            inputValues: new FormControl(dataItem.inputValues, [Validators.required,  InputValuesValidator]),
            isGridEligible: new FormControl(dataItem.isGridEligible, Validators.required),
            isInvoiceEligible: new FormControl(dataItem.isInvoiceEligible, Validators.required),
        });

An example function where I use this FormGroup

    public editRows(grid) {      
      this.isEdited = true;
      let currentRow = 0;
      let rows: any = grid.data.data;
  
      for (let i = 0; i < rows.length; i++) {
        const formGroup = this.createFormGroup(rows[i]);          
        this.formGroup = formGroup;
        (this.formGroups.get('items') as FormArray).push(formGroup);        
        grid.editRow(currentRow, formGroup, {skipFocus: true});
        currentRow++;
      }      
    }
2 Answers

Instead of checking it while submitting, just check it while filling form only and show the error immediately if user has filled duplicate value.

Working demo here.

use this function to check duplicacy -

checkDuplicacy(event, field: FormControl) {
    let length = this.formGroups.value.items.length;
    let count = 0;
    let controls = (<FormArray>this.formGroups.controls.items).controls;
    for (let i = 0; i < length; i++) {
      if (
        this.formGroups.value.items[i].attributeDisplayName.toLowerCase() ==
        event.target.value.toLowerCase()
      ) {
        count++;
      }
      if (count > 1) {
        field.markAsTouched();
        field.setValidators((f) => <any>{ duplicateName: true });
        field.updateValueAndValidity();
      } else {
        field.clearValidators();
        field.setValidators([Validators.required]);
        field.updateValueAndValidity();
      }
    }
  }

And Updated HTML -

    <kendo-grid-column field="attributeDisplayName" title="CUSTOM FIELD LABEL" width="190">  
<ng-template kendoGridEditTemplate let-column="column" let-formGroup="formGroup" >          
  <input ngDefaultControl  class="k-textbox" [formControl]="formGroup.get(column.field)" (blur)="checkDuplicacy($event, formGroup.get(column.field))">
  

      <div *ngIf="formGroup.get(column.field).errors && (formGroup.get(column.field).dirty || formGroup.get(column.field).touched)">
            <span style="color:red" class="k-icon k-i-warning"></span>
            <span style="color:red">CUSTOM FIELD LABEL is a required field</span>
            
      </div>
    
      <span *ngIf="formGroup.get(column.field).errors?.duplicateName" style="color:red">
      Duplicate field.</span>
    
    </ng-template>
    </kendo-grid-column>

Of course you can use a validator to check the duplicates. The validator is like

  validateUniq(index) {
    return (control: AbstractControl) => {
      if (control.value) {

        //search the "formArray"
        const formArray = control.parent
          ? (control.parent.parent as FormArray)
          : null;

        if (formArray) {
          //we create an array with the attributeDisplayNames
          const attributes = formArray.value.map((x) => x.attributeDisplayName);
      
          //only give error if there're duplicate before our
          //control
      
          return attributes.indexOf(control.value)>=0 && 
                 attributes.indexOf(control.value)<index
            ? { duplicateName: true }
            : null;
        }
      }
    };
  }

You create the formGroup like -see how you pass the "index"

  initFormField() {
    const index = this.items ? this.items.length : 0;
    return this.fb.group({
      attributeDisplayName: [
        '',
        [Validators.required, this.validateUniq(index)],
      ],
    });
  }

The problem when we use a Validator over a control is that only is checked when the control changes, so we need make a function checkDuplicacy like @Shyam say -but in this case is more simple:

  checkDuplicacy(index) {
    this.items.controls.forEach((x,i)=>{
      if (index!=i)
        (x as FormGroup).get('attributeDisplayName').updateValueAndValidity()
    })
  }

The last piece to completate the jigsaw is a function to get the control

  getAtributeDisplayNameAt(index: number) {
    return this.items
      ? (this.items.at(index).get('attributeDisplayName') as FormControl)
      : null;
  }

And an html like

<form [formGroup]="formGroups" novalidate autocomplete="off">
  <div formArrayName="items">
    <div
      *ngFor="let item of items.controls; let index = index"
      [formGroupName]="index"
    >
      <div class="form-control">
        <input
          placeholder="Enter here"
          formControlName="attributeDisplayName" 
          (input)="checkDuplicacy(index)"
        />
        <span
          *ngIf="getAtributeDisplayNameAt(index)?.errors?.duplicateName"
          class="error"
        >
          Duplicate field.</span
        >
      </div>
    </div>
  </div>
  <button (click)="addFormField()">Add More</button>
</form>

See how we call to the function checkDuplicacy in the event input -you can subscribe also to the valueChange of the control

The stackblitz

Related