admin管理员组

文章数量:1291392

I have several form arrays and I need a validation so that a particular field in each form row has to be unique across all the form arrays. If any of the values appear more than once, both form fields have to be marked in red.

I managed to write a function so that if there are any changes to those fields, the function returns true/false. But I'm unsure how to use this for the actual validation process.

ponent.html:

<div formArrayName="temperatureFormArr">
  <div class="row" *ngFor="let temperature of parentForm['controls'].detailForm['controls'].temperatureFormArr['controls']; let i=index"
    [formGroupName]="i">
    <div class="col-4">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Higher Level Function</mat-label>
        <input autoplete = "off" matInput placeholder="Higher Level Function" (input)="testFunction(i, 'temperatureFormArr')" formControlName="higherLevelFunction">
      </mat-form-field>
    </div>
    <div class="col-6">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Description</mat-label>
        <input autoplete = "off" matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
  </div>
</div>

<div formArrayName="waterPressureFormArr">
  <div class="row" *ngFor="let waterPressure of parentForm['controls'].detailForm['controls'].waterPressureFormArr['controls']; let i=index"
    [formGroupName]="i">
    <div class="col-4">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Higher Level Function</mat-label>
        <input autoplete = "off" matInput placeholder="Higher Level Function" (input)="testFunction(i, 'waterPressureFormArr')"  formControlName="higherLevelFunction">
      </mat-form-field>
    </div>
    <div class="col-6">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Description</mat-label>
        <input autoplete = "off" matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
  </div>
</div>

ponent.ts

  testFunction(i: any, typeOfArray: string) {
    var duplicateFlag = false;
    var testValue = this.parentForm['controls'].detailForm['controls'][typeOfArray].controls[i].value;
    for (let index = 0; index < this.parentForm['controls'].detailForm['controls'].temperatureFormArr.length; index++) {
      if(this.parentForm['controls'].detailForm['controls'].temperatureFormArr['controls'][index]
      .get('higherLevelFunction').value == testValue.higherLevelFunction && (index != i || typeOfArray != 'temperatureFormArr')) {
        duplicateFlag = true;
      }
    }
    for (let index = 0; index < this.parentForm['controls'].detailForm['controls'].waterPressureFormArr.length; index++) {    
      if(this.parentForm['controls'].detailForm['controls'].waterPressureFormArr['controls'][index]
      .get('higherLevelFunction').value == testValue.higherLevelFunction && (index != i || typeOfArray != 'waterPressureFormArr')) {
        duplicateFlag = true;
      }
    }
    return duplicateFlag;
  }

For every keystroke, this function is called and a check is done against all the values in all other form arrays to see if there are any duplicates. But how do I change it so that I can use this function as a validator and mark the duplicate input fields accordingly?

I have several form arrays and I need a validation so that a particular field in each form row has to be unique across all the form arrays. If any of the values appear more than once, both form fields have to be marked in red.

I managed to write a function so that if there are any changes to those fields, the function returns true/false. But I'm unsure how to use this for the actual validation process.

ponent.html:

<div formArrayName="temperatureFormArr">
  <div class="row" *ngFor="let temperature of parentForm['controls'].detailForm['controls'].temperatureFormArr['controls']; let i=index"
    [formGroupName]="i">
    <div class="col-4">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Higher Level Function</mat-label>
        <input autoplete = "off" matInput placeholder="Higher Level Function" (input)="testFunction(i, 'temperatureFormArr')" formControlName="higherLevelFunction">
      </mat-form-field>
    </div>
    <div class="col-6">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Description</mat-label>
        <input autoplete = "off" matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
  </div>
</div>

<div formArrayName="waterPressureFormArr">
  <div class="row" *ngFor="let waterPressure of parentForm['controls'].detailForm['controls'].waterPressureFormArr['controls']; let i=index"
    [formGroupName]="i">
    <div class="col-4">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Higher Level Function</mat-label>
        <input autoplete = "off" matInput placeholder="Higher Level Function" (input)="testFunction(i, 'waterPressureFormArr')"  formControlName="higherLevelFunction">
      </mat-form-field>
    </div>
    <div class="col-6">
      <mat-form-field appearance="outline" class="fex-input">
          <mat-label>Description</mat-label>
        <input autoplete = "off" matInput placeholder="Description" formControlName="description">
      </mat-form-field>
    </div>
  </div>
</div>

ponent.ts

  testFunction(i: any, typeOfArray: string) {
    var duplicateFlag = false;
    var testValue = this.parentForm['controls'].detailForm['controls'][typeOfArray].controls[i].value;
    for (let index = 0; index < this.parentForm['controls'].detailForm['controls'].temperatureFormArr.length; index++) {
      if(this.parentForm['controls'].detailForm['controls'].temperatureFormArr['controls'][index]
      .get('higherLevelFunction').value == testValue.higherLevelFunction && (index != i || typeOfArray != 'temperatureFormArr')) {
        duplicateFlag = true;
      }
    }
    for (let index = 0; index < this.parentForm['controls'].detailForm['controls'].waterPressureFormArr.length; index++) {    
      if(this.parentForm['controls'].detailForm['controls'].waterPressureFormArr['controls'][index]
      .get('higherLevelFunction').value == testValue.higherLevelFunction && (index != i || typeOfArray != 'waterPressureFormArr')) {
        duplicateFlag = true;
      }
    }
    return duplicateFlag;
  }

For every keystroke, this function is called and a check is done against all the values in all other form arrays to see if there are any duplicates. But how do I change it so that I can use this function as a validator and mark the duplicate input fields accordingly?

Share Improve this question asked Oct 27, 2019 at 6:41 InceptionInception 4651 gold badge11 silver badges32 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 6

You has two aproach when use validations

1.-Validate the control and in (input) call to updateValueAndValidity of the rest of the controls. This is because when you make a validation over a control only validate this control.

You can use a function like

  duplicateControlError(field) {
    return (control: FormControl) => {
      let result: boolean = false;
      const group = control.parent as FormGroup;
      if (group) {
        const values = control.parent.parent.value.map(x => x[field]);
        result = values.filter(x => x == control.value).length > 1;
      }
      return result ? { error: "duplicate" } : null;
    };
  }

And another to updateValueAndValidity the other controls

  updateValidation(arrayName,field)
  {
    (this.form.get(arrayName) as FormArray).controls.forEach(
      group=>group.get(field).updateValueAndValidity()
    )
  }

The .html bees like

        <mat-form-field class="example-full-width">
            <input matInput placeholder="higherLevelFunction" formControlName="higherLevelFunction" (input)="updateValidation('temperatureFormArray','higherLevelFunction')">
<mat-error>Duplicate</mat-error>
</mat-form-field>

And you create the form like

  form = new FormGroup({
    temperatureFormArray: new FormArray(
      this.data.map(
        (x, index) =>
          new FormGroup({
            description: new FormControl(x.description),
            higherLevelFunction: new FormControl(
              x.higherLevelFunction,
              this.duplicateControlError("higherLevelFunction")
            )
          })
      )
    )
  });

The other aproach is make a custom validator over the FormArray

  duplicateError(field) {
    return (formArray: FormArray) => {
      let duplicate = [];
      formArray.value.forEach((x, index) => {
        if (formArray.value.filter(y => y[field] == x[field]).length > 1)
          duplicate.push(index);
      });
      return duplicate.length ? { error: duplicate } : null;
    };
  }

The formArray you create like

form = new FormGroup({
    temperatureFormArray: new FormArray(
      this.data.map(
        (x, index) =>
          new FormGroup({
            description: new FormControl(x.description),
            higherLevelFunction: new FormControl(
              x.higherLevelFunction)
            )
          })
      ),
      this.duplicateError("higherLevelFunction")
    )
  });

The problem with this aproach is that is the FormArray who has the error. If only has a input normal, we can use some like

    <div *ngIf="form.get('temperatureFormArray').errors?.error.indexOf(i)>=0">
      duplicate
    </div>

But we are using material input, so we need change when you mark a control as invalid. For this we need use a custom ErrorStateMatcher. This is only a function that make mat-error show if the function return true. We need pass as argument the formArrayName and the index, so we has defined some like

export class DuplicateStateMatcher implements ErrorStateMatcher {
  constructor(private formArrayName:string,private index:number){}
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const formArray=form.form.get(this.formArrayName) as FormArray
    const error=formArray && formArray.errors?
    formArray.errors.error.indexOf(this.index)>=0:null
    return (control && error && (control.dirty || control.touched));
  }
}

And a funciton in our ponent like

  matcher(formArrayName,index)
  {
     return new DuplicateStateMatcher(formArrayName,index);
  }  

The .html

        <mat-form-field class="example-full-width">
            <input matInput placeholder="higherLevelFunction" formControlName="higherLevelFunction" [errorStateMatcher]="matcher('temperatureFormArray',i)">
<mat-error>Duplicate</mat-error>
</mat-form-field>

You can see the two aproach in stackblitz

Angular forms can have custom validators. Essentially, a function that performs logic to decide if the field is valid or not. Here's a how to guide. Use the same logic you have in the testFunction and apply it the the custom validator. One built, apply it to all the fields in the arrays.

本文标签: javascriptValidation to prevent duplicate form values in angular formsStack Overflow