Я создал специальный валидатор для проверки уникальности в моем FormArray. Я хочу показать ошибку, когда конкретные значения находятся в массиве.

Проблема в том, что он не работает, как ожидалось.

Фактическое поведение:

Действия по воспроизведению:

  • Добавить 3 «входы» - адрес;
  • Заполните ввод 1;
  • Заполните вход 2 другим значением;
  • Заполните вход 3 тем же значением, что и вход 1; (ошибок нет, ни на входе 1, ни на входе 3)

Ожидаемое поведение .

Если те же самые значения появляются в «X группах», их конкретные входы должны показывать ошибку.

В описанном выше случае ошибки должны появиться на входах 1 и 3.

Предположим, что у меня есть 4 входа:

  1. значение: стек
  2. значение: переполнение
  3. значение: стек
  4. значение: переполнение

4 входа должны показывать ошибку, потому что все они являются дубликатами.


static uniqueBy = (field: string, caseSensitive = true): ValidatorFn => {
  return (formArray: FormArray): { [key: string]: boolean } => {
    const controls = formArray.controls.filter(formGroup => {
      return isPresent(formGroup.get(field).value);
    });
    const uniqueObj = { uniqueBy: true };
    let found = false;

    if (controls.length > 1) {
      for (let i = 0; i < controls.length; i++) {
        const formGroup = controls[i];
        const mainControl = formGroup.get(field);
        const val = mainControl.value;    
        const mainValue = caseSensitive ? val.toLowerCase() :  val;

        controls.forEach((group, index) => {
          if (i === index) {
            // Same group
            return;
          }

          const currControl = group.get(field);
          const tempValue = currControl.value;
          const currValue = caseSensitive ? tempValue.toLowerCase() : tempValue;
          let newErrors;

          if ( mainValue === currValue) {
            if (isBlank(currControl.errors)) {
              newErrors = uniqueObj;
            } else {
              newErrors = Object.assign(currControl.errors, uniqueObj);
            }

            found = true;
          } else {
            newErrors = currControl.errors;

            if (isPresent(newErrors)) {
              // delete uniqueBy error
              delete newErrors['uniqueBy'];

              if (isBlank(newErrors)) {
                // {} to undefined/null
                newErrors = null;
              }
            }
          }

          // Add specific errors based on condition
          currControl.setErrors(newErrors);
        });
      }

      if (found) {
        // Set errors to whole formArray
        return uniqueObj;
      }
    }

    // Clean errors
    return null;
  };
}

Вы можете проверить это здесь DEMO .

7
dev_054 24 Апр 2017 в 03:10

2 ответа

Лучший ответ

В вашем коде используется вложенный цикл for, в котором вы чередуете ошибки.

Вот как выглядит состояние проверки для каждой итерации:

  0      [null, "{"uniqueBy":true}", null]

  1      ["{"uniqueBy":true}", "{"uniqueBy":true}", null]

  2      [null, "{}", null]

http://plnkr.co/edit/MTjzQ9KiJHJ56DVAZ155?p=preview (добавьте три адреса и соблюдайте вывод)

В приведенном ниже коде я очищаю ошибки только один раз для оператора цикла и больше не удаляю ошибки.

controls.map(formGroup => formGroup.get(field)).forEach(x => x.errors && delete x.errors['uniqueBy']);
for (let i: number = 0; i < controls.length; i++) {
    const formGroup: FormGroup = controls[i] as FormGroup;
    const mainControl: AbstractControl = formGroup.get(field);
    const val: string = mainControl.value;

    const mainValue: string = caseSensitive ? val.toLowerCase() :  val;
    controls.forEach((group: FormGroup, index: number) => {
        if (i === index) {
            return;
        }

        const currControl: any = group.get(field);
        const tempValue: string = currControl.value;
        const currValue: string = caseSensitive ? tempValue.toLowerCase() : tempValue;
        let newErrors: any;

        if ( mainValue === currValue) {
            if (isBlank(currControl.errors)) {
                newErrors = uniqueObj;
            } else {
                newErrors = Object.assign(currControl.errors, uniqueObj);
            }
            currControl.setErrors(newErrors);
            find = true;
        }
    });
}

Пример плунжера

10
yurzui 24 Апр 2017 в 15:35

Я не мог добавить комментарий, поэтому я просто добавляю это как новый ответ. Я немного изменил задатчик юрзуи, так как форма оставалась недействительной после исправления поля с повторяющимся значением.

Как воспроизвести ошибку в поршне юрзуи:

  1. Введите правильное имя
  2. Заполните улицу значением 'x'
  3. Добавьте новый адрес со значением улицы 'x'
  4. Измените вторую улицу на что-то отличное от «х»

Форма по-прежнему недействительна, потому что в первом 'Street' FormControl для ошибок установлено значение {{}, а не ноль. Я только что заметил, что dev_054 сделал это в своем первоначальном посте, но в любом случае, вот плункер с изменениями: http : //plnkr.co/edit/nQeQ01fjnTlcp3FgEnWL р = предварительный просмотр

Код был:

controls.map(formGroup => formGroup.get(field)).forEach(x => x.errors && delete x.errors['uniqueBy']);

Код сейчас:

controls.map(formGroup => formGroup.get(field)).forEach(x => {
    if (x.errors) {
        delete x.errors['unique-by'];
        if (isBlank(x.errors)) {
            x.setErrors(null);
        }
    }
});

В поршне я также исправил способ интерпретации параметра caseSensitive. Это будет работать, как исключено сейчас.

6
Marcos Dimitrio 30 Апр 2018 в 16:21