Angular2 валидатор, который опирается на несколько полей формы

Можно ли создать валидатор, который может использовать несколько значений, чтобы решить, действительно ли мое поле?

например. если клиент предпочитает контактный метод по электронной почте, тогда поле электронной почты должно быть обязательным.

Спасибо.


Обновлен с помощью кода примера...


import {Component, View} from 'angular2/angular2';
import {FormBuilder, Validators, formDirectives, ControlGroup} from 'angular2/forms';

@Component({
    selector: 'customer-basic',
    viewInjector: [FormBuilder]
})
@View({
    templateUrl: 'app/components/customerBasic/customerBasic.html',
    directives: [formDirectives]
})
export class CustomerBasic {
    customerForm: ControlGroup;

    constructor(builder: FormBuilder) {
        this.customerForm = builder.group({
            firstname: [''],
            lastname: [''],
            validateZip: ['yes'],
            zipcode: ['', this.zipCodeValidator] 
            // I only want to validate using the function below if the validateZip control is set to 'yes'
        });
    }

    zipCodeValidator(control) {
        if (!control.value.match(/\d\d\d\d\d(-\d\d\d\d)?/)) {
            return { invalidZipCode: true };
        }
    }

}

Ответ 1

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

В этом примере просто укажите имена ключей полей password и confirmPassword.

// Example use of FormBuilder, FormGroups, and FormControls
this.registrationForm = fb.group({
  dob: ['', Validators.required],
  email: ['', Validators.compose([Validators.required,  emailValidator])],
  password: ['', Validators.required],
  confirmPassword: ['', Validators.required],
  firstName: ['', Validators.required],
  lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})

Чтобы Validators принимать параметры, им нужно вернуть a function либо с помощью FormGroup, либо FormControl в качестве параметра. В этом случае я проверяю FormGroup.

function matchingPasswords(passwordKey: string, confirmPasswordKey: string) {
  return (group: FormGroup): {[key: string]: any} => {
    let password = group.controls[passwordKey];
    let confirmPassword = group.controls[confirmPasswordKey];

    if (password.value !== confirmPassword.value) {
      return {
        mismatchedPasswords: true
      };
    }
  }
}

Технически, я мог бы проверить любые два значения, если бы знал их ключи, но я предпочитаю называть мой Validators таким же, как ошибка, которую они возвратят. Функция может быть изменена, чтобы принять третий параметр, который представляет имя ключа возвращенной ошибки.

Обновлено 6 декабря 2016 г. (v2.2.4)

Полный пример: https://embed.plnkr.co/ukwCXm/

Ответ 2

Дэйв ответил очень, очень полезно. Однако небольшая модификация может помочь некоторым людям.

Если вам нужно добавить ошибки в поля Control, вы можете сохранить фактическое построение формы и валидаторов:

// Example use of FormBuilder, ControlGroups, and Controls
this.registrationForm= fb.group({
  dob: ['', Validators.required],
  email: ['', Validators.compose([Validators.required,  emailValidator])],
  password: ['', Validators.required],
  confirmPassword: ['', Validators.required],
  firstName: ['', Validators.required],
  lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})

Вместо того, чтобы устанавливать ошибку на ControlGroup, сделайте это в фактическом поле следующим образом:

function matchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
  return (group: ControlGroup) => {
    let passwordInput = group.controls[passwordKey];
    let passwordConfirmationInput = group.controls[passwordConfirmationKey];
    if (passwordInput.value !== passwordConfirmationInput.value) {
      return passwordConfirmationInput.setErrors({notEquivalent: true})
    }
  }
}

Ответ 3

Я использую Angular 2 RC.5, но не могу найти ControlGroup на основе полезного ответа от Dave. Я обнаружил, что FormGroup работает. Поэтому я сделал некоторые незначительные обновления кодов Дейва и думал, что буду делиться с другими.

В вашем файле компонента добавьте импорт для FormGroup:

import {FormGroup} from "@angular/forms";

Определите свои входы, если вам нужно напрямую получить доступ к элементу управления формой:

oldPassword = new FormControl("", Validators.required);
newPassword = new FormControl("", Validators.required);
newPasswordAgain = new FormControl("", Validators.required);

В своем конструкторе создайте экземпляр формы:

this.form = fb.group({
  "oldPassword": this.oldPassword,
  "newPassword": this.newPassword,
  "newPasswordAgain": this.newPasswordAgain
}, {validator: this.matchingPasswords('newPassword', 'newPasswordAgain')});

Добавьте функцию matchPasswords в свой класс:

matchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
  return (group: FormGroup) => {
    let passwordInput = group.controls[passwordKey];
    let passwordConfirmationInput = group.controls[passwordConfirmationKey];
    if (passwordInput.value !== passwordConfirmationInput.value) {
      return passwordConfirmationInput.setErrors({notEquivalent: true})
    }
  }
}

Надеюсь, это поможет тем, кто использует RC.5. Обратите внимание, что я еще не тестировал RC.6.

Ответ 4

Развернуть на matthewdaniel ответ, так как это не совсем правильно. Вот пример кода, который показывает, как правильно назначить валидатор для ControlGroup.

import {Component} from angular2/core
import {FormBuilder, Control, ControlGroup, Validators} from 'angular2/common'

@Component({
  selector: 'my-app',
  template: `
    <form [ngFormModel]="form">
      <label for="name">Name:</label>
      <input id="name" type="text" ngControl="name">
      <br>
      <label for="email">Email:</label>
      <input id="email" type="email" ngControl="email">
      <br>
      <div ngControlGroup="matchingPassword">
        <label for="password">Password:</label>
        <input id="password" type="password" ngControl="password">
        <br>
        <label for="confirmPassword">Confirm Password:</label>
        <input id="confirmPassword" type="password" ngControl="confirmPassword">
      </div>
    </form>
    <p>Valid?: {{form.valid}}</p>
    <pre>{{form.value | json}}</pre>
  `
})
export class App {
  form: ControlGroup
  constructor(fb: FormBuilder) {
    this.form = fb.group({
      name: ['', Validators.required],
      email: ['', Validators.required]
      matchingPassword: fb.group({
        password: ['', Validators.required],
        confirmPassword: ['', Validators.required]
      }, {validator: this.areEqual})
    });
  }

  areEqual(group: ControlGroup) {
    let val;
    let valid = true;

    for (name in group.controls) {
      if (val === undefined) {
        val = group.controls[name].value
      } else {
        if (val !== group.controls[name].value) {
          valid = false;
          break;
        }
      }
    }

    if (valid) {
      return null;
    }

    return {
      areEqual: true
    };
  }
}

Вот рабочий пример: http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview

Ответ 5

Множество копаний в источнике angular, но я нашел лучший способ.

constructor(...) {
    this.formGroup = builder.group({
        first_name:        ['', Validators.required],
        matching_password: builder.group({
            password: ['', Validators.required],
            confirm:  ['', Validators.required]
        }, this.matchPassword)
    });

    // expose easy access to passworGroup to html
    this.passwordGroup = this.formGroup.controls.matching_password;
}

matchPassword(group): any {
    let password = group.controls.password;
    let confirm = group.controls.confirm;

    // Don't kick in until user touches both fields   
    if (password.pristine || confirm.pristine) {
      return null;
    }

    // Mark group as touched so we can add invalid class easily
    group.markAsTouched();

    if (password.value === confirm.value) {
      return null;
    }

    return {
      isValid: false
    };
}

Часть HTML для группы паролей

<div ng-control-group="matching_password" [class.invalid]="passwordGroup.touched && !passwordGroup.valid">
    <div *ng-if="passwordGroup.touched && !passwordGroup.valid">Passwords must match.</div>
    <div class="form-field">
        <label>Password</label>
        <input type="password" ng-control="password" placeholder="Your password" />
    </div>
    <div class="form-field">
        <label>Password Confirmation</label>
        <input type="password" ng-control="confirm" placeholder="Password Confirmation" />
    </div>
</div>

Ответ 6

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

Пожалуйста, ознакомьтесь с моей реализацией специального валидатора для Angular 2, который учитывает это: https://gist.github.com/slavafomin/17ded0e723a7d3216fb3d8bf845c2f30.

Я использую otherControl.valueChanges.subscribe() для прослушивания изменений в другом элементе управления и thisControl.updateValueAndValidity() для запуска очередного раунда проверки при изменении другого элемента управления.


Я копирую код ниже для справки в будущем:

матч-другой-validator.ts

import {FormControl} from '@angular/forms';


export function matchOtherValidator (otherControlName: string) {

  let thisControl: FormControl;
  let otherControl: FormControl;

  return function matchOtherValidate (control: FormControl) {

    if (!control.parent) {
      return null;
    }

    // Initializing the validator.
    if (!thisControl) {
      thisControl = control;
      otherControl = control.parent.get(otherControlName) as FormControl;
      if (!otherControl) {
        throw new Error('matchOtherValidator(): other control is not found in parent group');
      }
      otherControl.valueChanges.subscribe(() => {
        thisControl.updateValueAndValidity();
      });
    }

    if (!otherControl) {
      return null;
    }

    if (otherControl.value !== thisControl.value) {
      return {
        matchOther: true
      };
    }

    return null;

  }

}

Использование

Здесь вы можете использовать его с реактивными формами:

private constructForm () {
  this.form = this.formBuilder.group({
    email: ['', [
      Validators.required,
      Validators.email
    ]],
    password: ['', Validators.required],
    repeatPassword: ['', [
      Validators.required,
      matchOtherValidator('password')
    ]]
  });
}

Более свежие валидаторы можно найти здесь: moebius-mlm/ng-validators.

Ответ 7

Вот еще один вариант, который я смог придумать, который не зависит от целого или sub ControlGroup, но привязан непосредственно к каждому Control.

Проблема, с которой я столкнулась, заключалась в том, что элементы управления, зависящие друг от друга, не были иерархически вместе, поэтому я не смог создать ControlGroup. Кроме того, мой CSS был настроен на то, что каждый элемент управления будет использовать существующие классы angular, чтобы определить, следует ли отображать стилирование ошибок, что было более сложным при работе с групповой проверкой вместо проверки, специфичной для управления. Попытка определить, действителен ли один элемент управления, невозможен, поскольку проверка была привязана к группе элементов управления, а не к каждому отдельному элементу управления.

В моем случае мне понадобилось значение select box, чтобы определить, требуется ли другое поле или нет.

Это построено с использованием Form Builder для компонента. Для выбранной модели вместо прямого привязки ее к значению объекта запроса я связал ее с функциями get/set, которые позволят мне обрабатывать события "on change" для элемента управления. Затем я смогу вручную установить проверку для другого элемента управления в зависимости от нового элемента управления select.

Вот соответствующая часть обзора:

<select [ngFormControl]="form.controls.employee" [(ngModel)]="employeeModel">
  <option value="" selected></option>
  <option value="Yes">Yes</option>
  <option value="No">No</option>
</select>
...
<input [ngFormControl]="form.controls.employeeID" type="text" maxlength="255" [(ngModel)]="request.empID" />

Соответствующая часть компонента:

export class RequestComponent {
  form: ControlGroup;
  request: RequestItem;

  constructor(private fb: FormBuilder) {
      this.form = fb.group({
        employee: new Control("", Validators.required),
        empID: new Control("", Validators.compose([Validators.pattern("[0-9]{7}"]))
      });

  get employeeModel() {
    return this.request.isEmployee;
  }

  set employeeModel(value) {
    this.request.isEmployee = value;
    if (value === "Yes") {
      this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}"), Validators.required]);
      this.form.controls["empID"].updateValueAndValidity();
    }
    else {
      this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}")]);
      this.form.controls["empID"].updateValueAndValidity();
    }
  }
}

В моем случае у меня всегда была привязка к шаблону, привязанная к элементу управления, поэтому validator всегда имеет значение, но я думаю, вы можете установить значение validator равным null, если у вас нет привязки к элементу управления.

UPDATE: существуют другие способы захвата изменения модели, например (ngModelChange)=changeFunctionName($event) или подписки на изменения контрольных значений, используя this.form.controls["employee"].valueChanges.subscribe(data => ...))

Ответ 8

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

    this.password = new Control('', Validators.required);
    let x = this.password;
    this.confirm = new Control('', function(c: Control){
        if(typeof c.value === 'undefined' || c.value == "") return {required: "password required"};
        if(c.value !== x.value)
            return {error: "password mismatch"};
        return null;
    });

Я знаю, что это сильно зависит от версии angularjs2, которую вы используете. Это было протестировано против 2.0.0-alpha.46

Если у кого-то есть лучшее предложение, например, создание настраиваемого валидатора (что может быть лучшим способом), то это приветствуется.

ИЗМЕНИТЬ

вы также можете использовать ControlGroup и полностью проверить эту группу.

this.formGroup = new ControlGroup({}, function(c: ControlGroup){
        var pass: Control = <Control>c.controls["password"];
        var conf: Control = <Control>c.controls["confirm"];
        pass.setErrors(null, true);
        if(pass.value != null && pass.value != ""){
            if(conf.value != pass.value){
                pass.setErrors({error: "invalid"}, true);
                return {error: "error"};
            }
        }
        return null;
    });

Просто отредактируйте сообщения в соответствии с вашим доменом.

Ответ 9

Ответ Луи Круса был очень полезен для меня.

Для завершения просто добавьте в else setErrors reset: return passwordConfirmationInput.setErrors(null);

И все работает отлично!

Спасибо,

Привет,

ТГА

Ответ 10

Ищем это и закончил с помощью equalTo из пакета проверки ng2 (https://www.npmjs.com/package/ng2-validation)

Вот пример: Шаблон Driven:

<input type="password" ngModel name="password" #password="ngModel" required/>
<p *ngIf="password.errors?.required">required error</p>
<input type="password" ngModel name="certainPassword" #certainPassword="ngModel" [equalTo]="password"/>
<p *ngIf="certainPassword.errors?.equalTo">equalTo error</p>

Модель:

let password = new FormControl('', Validators.required);
let certainPassword = new FormControl('', CustomValidators.equalTo(password));

this.form = new FormGroup({
  password: password,
  certainPassword: certainPassword
});

Шаблон:

<form [formGroup]="form">
  <input type="password" formControlName="password"/>
  <p *ngIf="form.controls.password.errors?.required">required error</p>
  <input type="password" formControlName="certainPassword"/>
  <p *ngIf="form.controls.certainPassword.errors?.equalTo">equalTo error</p>
</form>

Ответ 11

Вот моя версия, которую я использовал для обеспечения возраста в одном поле, больше или равна возрасту в другом поле. Я также использую группы форм, поэтому я использую функцию group.get, а не group.controls[]

import { FormGroup } from '@angular/forms';

export function greaterThanOrEqualTo(sourceKey: string, targetKey: string) {
    return (group: FormGroup) => {
        let sourceInput = group.get(sourceKey);
        let targetInput = group.get(targetKey);

        console.log(sourceInput);
        console.log(targetInput);

        if (targetInput.value < sourceInput.value) {
            return targetInput.setErrors({ notGreaterThanOrEqualTo: true })
        }
    }
}

И в компоненте:

    this.form = this._fb.group({

        clientDetails: this._fb.group({
            currentAge: ['', [Validators.required, Validators.pattern('^((1[89])|([2-9][0-9])|100)$')]],
            expectedRetirementAge: ['', [Validators.required]]
        }),

    },
    {
        validator: greaterThanOrEqualTo('clientDetails.currentAge', 'clientDetails.expectedRetirementAge')
    });

Ответ 12

Angular 4 правила проверки соответствия паролей.

Если вам нужны поля управления ошибками, вы можете это сделать.

createForm() {
    this.ngForm = this.fb.group({
       'first_name': ["", Validators.required ],
       'last_name' : ["", Validators.compose([Validators.required, Validators.minLength(3)]) ],
       'status' : ['active', Validators.compose([Validators.required])],
       'phone':[null],
       'gender':['male'],
       'address':[''],
       'email':['', Validators.compose([
          Validators.required, 
          Validators.email])],
       'password':['', Validators.compose([Validators.required])],
       'confirm_password':['', Validators.compose([Validators.required])]
    }, {validator: this.matchingPassword('password', 'confirm_password')});
  }

Тогда вам нужно объявить этот метод в методе constructor Как и как.

constructor(
    private fb: FormBuilder

    ) {
    this.createForm();
  }

Вместо того, чтобы устанавливать ошибку в ControlGroup, сделайте это в фактическом поле следующим образом:

    matchingPassword(passwordKey: string, confirmPasswordKey: string) {
  return (group: FormGroup): {[key: string]: any} => {
    let password = group.controls[passwordKey];
    let confirm_password = group.controls[confirmPasswordKey];

    if (password.value !== confirm_password.value) {
      return {        
        mismatchedPasswords: true
      };
    }
  }
}

Часть HTML для группы паролей

<form [formGroup]="ngForm" (ngSubmit)="ngSubmit()">
    <div class="form-group">
            <label class="control-label" for="inputBasicPassword"> Password <span class="text-danger">*</span></label>
                <input type="password" class="form-control" formControlName="password" placeholder="Password" name="password" required>
                <div class="alert text-danger" *ngIf="!ngForm.controls['password'].valid && ngForm.controls['password'].touched">This Field is Required.</div>
            </div>
            {{ngForm.value.password | json}}
            <div class="form-group">
            <label class="control-label" for="inputBasicPassword">Confirm Password <span class="text-danger">*</span></label>
                <input type="password" class="form-control" name="confirm_password" formControlName="confirm_password" placeholder="Confirm Password" match-password="password">

    <div class='alert text-danger' *ngIf="ngForm.controls.confirm_password.touched && ngForm.hasError('mismatchedPasswords')">
              Passwords doesn't match.
      </div>
    </div>
<button type="submit" [disabled]="!ngForm.valid" class="btn btn-primary ladda-button" data-plugin="ladda" data-style="expand-left" disabled="disabled"><span class="ladda-label">
            <i class="fa fa-save"></i>  Create an account
        <span class="ladda-spinner"></span><div class="ladda-progress" style="width: 0px;"></div>
        </span><span class="ladda-spinner"></span></button>
</form>