Angular 2 условных Validators.required?

Как мне следует условно требовать поле формы? Я сделал специальный валидатор, но условные переменные, которые я передаю в пользовательский валидатор, являются статическими и остаются их начальными значениями. Каким должен быть мой пользовательский валидатор для получения обновленных условных значений? Возможно, есть способ сделать это с помощью Validators.required вместо настраиваемого валидатора?

private foo: boolean = false;
private bar: boolean = true;

constructor(private _fb: FormBuilder) {
    function conditionalRequired(...conditions: boolean[]) {
      return (control: Control): { [s: string]: boolean } => {
        let required: boolean = true;
        for (var i = 0; i < conditions.length; i++) {
          if (conditions[i] === false) {
            required = false;
          }
        }
        if (required && !control.value) {
          return { required: true }
        }
      }
    }
    this.applyForm = _fb.group({
          'firstName': ['', Validators.compose([
            conditionalRequired(this.foo, !this.bar)
          ])],
          ...
    });
}

Обновление (17 мая 2016 года)

Прошло много времени с момента публикации, но я хотел бы ссылаться на методы .include() и .exclude(), доступные в классе ControlGroup для всех, кто пытается создать эту функцию. (docs). Хотя существуют вероятные варианты использования условного валидатора, как указано выше, я нашел включение и исключение элементов управления, групп управления, и управляющие массивы - отличный способ справиться с этим. Просто установите валидатор required на элемент управления, который вы хотите, и включите/исключите его, как вам будет угодно. Надеюсь, это поможет кому-то!

Ответы

Ответ 1

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

Для архивирования вам необходимо использовать объект для условий, описанных ниже:

private foo: boolean = false;
private bar: boolean = true;

private conditions: any = {
  condition1: foo,
  condition2: !bar
};

constructor(private _fb: FormBuilder) {
    function conditionalRequired(conditions: any) {
      return (control: Control): { [s: string]: boolean } => {
        let required: boolean = true;
        for (var elt in conditions) {
          var condition = conditions[elt];
          if (conditions === false) {
            required = false;
          }
        }
        if (required && !control.value) {
          return { required: true };
        }
      }
    }
    this.applyForm = _fb.group({
          'firstName': ['', Validators.compose([
            conditionalRequired(conditions)
          ])],
          ...
    });
}

Таким образом, параметр условий может использоваться/обновляться по ссылке. Чтобы обновить свои условия, вам необходимо сделать следующее:

updateConditions() {
  this.conditions.condition1 = true;
  this.conditions.condition2 = true;
}

Вот plunkr: https://plnkr.co/edit/bnX7p0?p=preview.

Edit

Чтобы запустить валидатор при обновлении условий, вам нужно явно вызвать метод updateValueAndValidity элемента управления. В этом случае атрибут valid как элемента управления, так и формы будет соответствующим образом обновлен:

updateConditions() {
  this.conditions.condition1 = true;
  this.conditions.condition2 = true;
  this.applyForm.controls.firstName.updateValueAndValidity();
}

Ответ 2

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

Сделано для rc.4 и нового модуля форм, часть revalidateOnChanges может быть ужасной (не уверена в лучшем способе вызвать это поведение), используйте на свой страх и риск!:)

Как использовать его

Валидатор принимает два аргумента, условную функцию, которая задается formGroup, и ожидается, что она вернет значение true, если валидация будет применяться и false в противном случае, и валидатор (который может быть композицией). Он обновит поле, когда будет обновлена ​​группа form, и теперь она может проверять только вещи внутри одной группы форм, но это должно быть легко исправить.

this.formBuilder.group({
    vehicleType: ['', Validators.required],
    licencePlate: [
        '',
        ExtraValidators.conditional(
            group => group.controls.vehicleType.value === 'car',
            Validators.compose([
                Validators.required,
                Validators.minLength(6)
            ])
        ),
    ]
});

В этом примере у вас есть два поля: vehicleType и licencePlate. В условном заявлении применяется составленный валидатор (требуется и minLength), если vehicleType является "автомобилем".

Вы можете использовать compose для применения нескольких разных условий, которые могут или не могут применяться одновременно. Вот несколько более сложный пример:

this.formBuilder.group({
    country: ['', Validators.required],
    vehicleType: ['', Validators.required],
    licencePlate: [
        '',
        Validators.compose([
            ExtraValidators.conditional(
                group => group.controls.vehicleType.value === 'car',
                Validators.required
            ),
            ExtraValidators.conditional(
                group => group.controls.country.value === 'sweden',
                Validators.minLength(6)
            ),
        ])
    ]
});

В этом случае мы применяем требуемый, если тип "автомобиль", и мы применяем minLength, если страна "Швеция". Если применяется только одно условие, только если применяется проверка, если оба условия применяются, применяются оба подтверждения.

Сам валидатор

Обратите внимание, что сравнение объектов - это просто грубая сила, потому что мы работаем с небольшим объектом, если вы используете Ramda или что-то, что вы могли бы сократить много кода.

export class ExtraValidators {
    static conditional(conditional, validator) {
        return function(control) {
            revalidateOnChanges(control);

            if (control && control._parent) {
                if (conditional(control._parent)) {
                    return validator(control);
                }
            }
        };
    }
}
function revalidateOnChanges(control): void {
    if (control && control._parent && !control._revalidateOnChanges) {
        control._revalidateOnChanges = true;
        control._parent
            .valueChanges
            .distinctUntilChanged((a, b) => {
                // These will always be plain objects coming from the form, do a simple comparison
                if(a && !b || !a && b) {
                    return false;
                } else if (a && b && Object.keys(a).length !== Object.keys(b).length) {
                    return false;
                } else if (a && b) {
                    for (let i in a) {
                        if(a[i] !== b[i]) {
                            return false;
                        }
                    }
                }
                return true;
            })
            .subscribe(() => {
                control.updateValueAndValidity();
            });

        control.updateValueAndValidity();
    }
    return;
}

ПРИМЕЧАНИЕ: не забудьте импортировать оператор:
import 'rxjs/add/operator/distinctUntilChanged';

Ответ 3

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

валидатор

export class ValidationService {
    static conditionalRequired = (isRequiredFunction: Function) => {
        return (control: Control) => {
           if (!control.value && isRequiredFunction())
           ...

Страница компонента

private myControl1: Control = 
    new Control("", ValidationService.conditionalRequired(() => 
        { return this && this.model && this.model.SomeProperty === 'SomeValue' } ));

private myControl2: Control = 
    new Control("", 
    ValidationService.conditionalRequired(this.isControl2Required.bind(this)));

isControl2Required() { 
    return someCondition;
}

Ответ 4

Мы можем использовать метод setValidators() для установки динамической валидации по требованию после нахождения элементов управления -

this.applyForm.get('firstName').setValidators(setRequired());

setRequired() {
        if(1==1) {
            return [Validators.required];
        } else {
            return [];
        }   
    }