Перекрестная проверка Angular FormArray между свойствами объекта

У меня есть Formgroup с Formarray внутри.

Это структура:

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 должны иметь БОЛЬШИЕ значения, чем следующий индекс.

so in myForm,

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

то же самое касается свойства «значение».

Как мне правильно реализовать настоящий валидатор (типа ValidatorFn) для этого formArray?

Пока мне удалось создать только функцию, которая проверяет каждое поле, сравнивает его с предыдущим и следующим, если значения не соответствуют правилам, я вручную установил ошибку с помощью setErrors()

Эта функция находится в подписке ValueChanges(), поэтому, когда значение в этом formArray изменяется, она проверяет его с помощью моей функции

Есть ли способ лучше?

Здесь stackblizt (подписка valueChanges не работает должным образом, она обновится только при записи в следующем поле, вы увидите, что я имею в виду в stackblitz)

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

Спасибо


person AJ989    schedule 05.12.2018    source источник
comment
Вы должны предоставить минимальный пример: пример, который только воспроизводит вашу проблему. В любом случае, я занимаюсь этим и создаю его, буду держать вас в курсе   -  person    schedule 05.12.2018
comment
Разве я этого не делал? В stackblitz я помещаю свое текущее решение и ищу способ сделать это, используя функции валидатора, предоставленные для FormGroups! Спасибо вам за помощь   -  person AJ989    schedule 05.12.2018
comment
Вы сделали это, но он не минимален, есть много бесполезных функций и HTML (что на самом деле нормально, просто заставляет людей сосредоточиться на других вещах). Вы уже являетесь частью 1%, которые предоставляют минимальные воспроизводимые примеры s, поэтому я не буду жаловаться на форма, я просто даю вам совет на следующий раз :)   -  person    schedule 05.12.2018
comment
хорошо, в следующий раз будет лучше! Спасибо тебе за помощь   -  person AJ989    schedule 05.12.2018


Ответы (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;
    }
  }
}
person Community    schedule 05.12.2018
comment
большое спасибо! Отличный ответ! Я вижу, что для первого индекса нет проверок, поскольку у него нет предыдущего значения, которое было бы первым, разве это не проблема? Затем еще одна вещь, у меня будет раскрывающийся список с двумя вариантами, которые повлияют на этот formarray. Одному варианту понадобится этот валидатор, второму варианту понадобится противоположный валидатор, то есть первый индекс должен быть ниже, чем следующий индекс, для этого можно просто обернуть ваше условие if () в другой оператор if / else? поэтому, если раскрывающийся список = 1, укажите значение ›previousValue, иначе, значение‹ previousValue еще раз спасибо за помощь - person AJ989; 05.12.2018
comment
Хорошо, вы скажете мне, что это не мои данные: D Для меня это не будет проблемой, как вы это представили! как видите, я вызываю компонентную функцию для создания валидатора: вы можете просто добавить логическое значение к addValidators и изменить условие в соответствии с этим логическим значением. - person ; 05.12.2018