对象属性之间的Angular FormArray交叉验证

时间:2018-12-05 10:50:16

标签: javascript angular forms formarray

我有一个带有Formarray的Formgroup。

这是结构:

myForm = this.fb.group(
        {
            title: ["", [Validators.required, Validators.minLength(4)]],
            pairs: this.fb.array(
                this.fPairs.map(f =>
                    this.fb.group({
                        grade: [],
                        value: []
                    })
                )
            )
        }
    );

我映射的FormArray看起来像这样的onInit:

fPairs: Array<pairs> = [
        {grade: 0, value: 0},
        {grade: 0, value: 0},
        {grade: 0, value: 0},
        {grade: 0, value: 0}
    ];

我要实现的目标是,此FormArray的每个对象的每个属性都是表单中的输入字段,因此我需要执行以下操作的验证:

索引为0的对象属性必须具有比下一个索引更大的值。

myForm中,

pairs[0].score > pairs[1].score
pairs[1].score > pairs[2].score
pairs[2].score > pairs[3].score

属性“值”也一样。

如何为此ValidatorFn正确实现一个真实的验证器(类型formArray)?

到目前为止,我仅设法创建了一个函数,该函数检查每个字段,并将其与上一个和下一个字段进行比较,如果值不遵循规则,则我用setErrors()手动设置错误

此功能已订阅ValueChanges(),因此当formArray中的值更改时,它将使用我的功能对其进行检查

有更好的方法吗?

这里有一个堆栈闪电(valueChanges订阅无法正常工作,只有在下一个字段中写入时才会刷新,您会在堆栈闪电中看到我的意思)

https://stackblitz.com/edit/angular-mat-formfield-flex-layout-x9nksb

谢谢

1 个答案:

答案 0 :(得分:1)

因此,过了一会儿(很抱歉造成延迟),我制作了stackblitz来再现您的最小示例,并为您制作了一个验证器。代码在我的答案结尾。

为向您简要说明:跨控件验证必须在父级中进行。父级可以是表单组或表单数组。在您的情况下,那将是一个表单数组(包含您所有分数的数组)。

然后将错误直接添加到您的表单数组,当满足条件时将其变为无效。

如您所见,控件对它们的错误一无所知:我是自愿这样做的,所以我不为您工作。我的最终目标是向您展示如何比较两个字段并相应地设置表格错误。

现在,在验证器中,您可以根据需要添加/删除控件中的错误,但是我想我已经回答了您有关两个单独字段上的表单验证的第一个问题。

代码是自我解释的,但是如果您有任何疑问,请随时提问!

(PS:如果要查看错误,将在stackblitz页面的页脚中显示错误)

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, ValidatorFn } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `
<form novalidate [formGroup]="form" fxLayout="column" fxLayoutGap="24px">
    <div formArrayName="pairs" fxLayout="row" fxLayoutGap="12px" *ngFor="let pair of form.get('pairs').controls; let i = index">
        <ng-container [formGroupName]="i">
            <mat-form-field fxFlex="50%">
                <input matInput type="text" formControlName="grade" placeholder="Grade for {{ i }}">
      </mat-form-field>
      <mat-form-field fxFlex="50%">
        <input matInput type="text" formControlName="value"  placeholder="Score for {{ i }}">
      </mat-form-field>
    </ng-container>
  </div>
</form>

<p>The form is {{ form.invalid ? 'invalid' : 'valid' }}</p>
<p>The pairs group is {{ form.get('pairs').invalid ? 'invalid' : 'valid' }}</p>

<p>Errors on the form : {{ form.errors | json }}</p>
<p>Errors on the group : {{ form.get('pairs').errors | json }}</p>
`,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  form: FormGroup;

  // Create a dataset
  data = [
    { grade: 6, value: 0 },
    { grade: 5, value: 0 },
    { grade: 4, value: 0 },
    { grade: 3, value: 0 },
  ];

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    // Create a form group
    this.form = this.fb.group({
      // Create a form array made of form groups
      pairs: this.fb.array(this.data.map(item => this.fb.group(item)))
    });

    // Add validators (optional, used to split the code logic)
    this.addValidators();
  }

  addValidators() {
    // Get the form array and append a validator (again code split)
    (this.form.get('pairs') as FormArray).setValidators(this.formArrayValidator());
  }

  // Form validator
  formArrayValidator(): ValidatorFn {
    // The validator is on the array, so the AbstractControl is of type FormArray
    return (group: FormArray) => {
      // Create an object of errors to return
      const errors = {};
      // Get the list of controls in the array (which are FormGroups)
      const controls = group.controls;
      // Iterate over them
      for (let i = 1; i < controls.length; i++) {
        // Get references to controls to compare them (split code again)
        const valueControl = controls[i].get('value');
        const previousValueControl = controls[i - 1].get('value');

        // if error, set array error
        if (valueControl.value > previousValueControl.value) {
          // array error (sum up of all errors)
          errors[i + 'greaterThan' + (i - 1)] = true;
        }
      }

      // return array errors ({} is considered an error so return null if it is the case)
      return errors === {} ? null : errors;
    }
  }
}