Angular2验证器,它依赖于多个表单字段


118

是否可以创建一个可以使用多个值来确定我的字段是否有效的验证器?

例如,如果客户首选的联系方式是通过电子邮件,则必须填写电子邮件字段。

谢谢。


更新了示例代码...


    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 };
            }
        }

    }

是。如果您向我们展示您的代码,我们可以添加一个特定的答案。
michelem

我添加了一个基本示例。在示例代码中,如果先前的validateZip控件包含“是”,我如何才能仅验证邮政编码?
西蒙(Simon)

西蒙,为什么不提您问题答案?
superjos

6
好的,为了让以后这个问题的访问者免于沮丧,我强烈建议您使用以下NPM软件包:npmjs.com/package/ng2-validation。它具有内置equalequalTo方法以及完善的文档!
米开朗基罗

Answers:


147

为了重申其他已发布的方法,这就是我一直在创建FormGroup不涉及多个组的验证器的方式。

对于此示例,只需提供passwordconfirmPassword字段的键名。

// 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获取参数,他们需要function使用FormGroupFormControl作为参数返回a 。在这种情况下,我正在验证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为它们将返回的错误。可以修改该函数以采用第三个参数,该参数表示返回的错误的键名。

更新于2016年12月6日(v2.2.4)

完整示例:https//embed.plnkr.co/ukwCXm/


@Dave << 涉及多个组>>实际上是指<<涉及多个组>>还是什么?谢谢
superjos

当密码与Angular 2 RC.1匹配时,这似乎并没有删除警报标记
datatype_void

“ ControlGroups”在2.0中似乎不存在。我使用了“ FormGroup”
Stephen

@superjos我确实是想这么说的。一些开发人员选择创建一个嵌套FormGroup来处理多字段验证,而不是一味地做Validator
cyber_dave

1
如果我们有密码,确认密码和电子邮件以及确认电子邮件该怎么办? [{validator: matchingPasswords('password', 'confirmPassword')},{validator: matchingEmail('email', 'confirmemail')}] 我试过了,但是没有用。有什么建议 ?@Dave
Sharan Ainapurapu

51

戴夫的回答非常非常有帮助。但是,稍加修改可能会对某些人有所帮助。

如果需要在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})
    }
  }
}

6
使用passwordConfirmationInput.setErrors(passwordConfirmationInput.validator(passwordConfirmationInput))else分支,使其正确更新时更改passwordInput使数据有效。
andraaspar'9

@andraaspar我尝试过,但是我得到了错误TypeError: passwordConfirmationInput.validator is not a function。这是因为我没有使用Validators.required显式创建FormControl。我将验证器留为空白,而是在输入中使用了“ required”属性。
beardedlinuxgeek

6
这很有用,但是我注意到angular文档的返回类型为{[key: string]: any}setErrors(...)不再返回(不再吗?)。还setErrors(...)覆盖已经存在任何错误,所以附加到当前错误对象,如:let errors = formGroup.controls[passwordConfirmationKey].errors;if(!errors) errors={};errors['notEquivalent'] = true;formGroup.controls[dateControlFirst].setErrors(errors);
斯蒂芬

32

当为多个表单字段实现验证器时,必须确保在更新每个表单控件时重新评估验证器。大多数示例都没有提供针对这种情况的解决方案,但这对于数据一致性和正确行为非常重要。

请参阅我对Angular 2的自定义验证器的实现,其中考虑了这一点:https : //gist.github.com/slavafomin/17ded0e723a7d3216fb3d8bf845c2f30

otherControl.valueChanges.subscribe()用来监听其他控件的更改,并在其他控件更改时thisControl.updateValueAndValidity()触发另一轮验证。


我正在复制下面的代码以供将来参考:

匹配其他验证器

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


不错的答案!我一直在寻找这样的解决方案好几个小时!请考虑一下变化:返回函数时不要丢失“ this”引用,而是返回如下函数:return(control:FormControl)=> {/ * code * /}
Vingtoft

很高兴我能帮助你。为什么需要参考this?实际上,具有用于调试目的的命名函数是很好的。
Slava Fomin II

性能?它正在工作,但是性能方面,我认为这不是很好的解决方案。当'theOtherControl'的值改变时更新'thisControl'会创建一个循环,不是吗?
nightElf91

什么时候应该退订?othercontrol.valuechanges.subscribe没有在任何地方退订。
Juana Pu

@juanapu我假设Angular 在销毁valueChanges时将终止可观察对象otherControl,这将导致订阅也被终止。但是,您的担心可能是有效的。我建议使用各种测试用例,使用最新版本的Angular彻底调试此代码。请报告,如果您发现任何问题。
Slava Fomin II

23

我正在使用Angular 2 RC.5,但根据Dave的有用答案,找不到ControlGroup。我发现FormGroup代替了。因此,我对Dave的代码做了一些小的更新,并认为我会与他人分享。

在您的组件文件中,为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')});

在您的课程中添加matchingPasswords函数:

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上进行测试。


@Sam是否进行了某些更改以使其与最终版本兼容?它说:类型为'{validator:(group:FormGroup)=> void; }”不能分配给“ ValidatorFn”类型的参数。
xtof

不,我不需要更改任何内容-对我来说,上面的示例代码可与Angular2 final一起使用。您是否使用上面的确切代码?
山姆

好的解决方案@Chang。如果您在填写确认密码后更改密码。验证无效。您可以尝试if (passwordInput.value !== passwordConfirmationInput.value) { return passwordConfirmationInput.setErrors({ notEquivalent: true }); } else { return passwordConfirmationInput.setErrors(null); }
Mario Shtika

16

大量研究角度源,但是我找到了一种更好的方法。

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>

当运行matching_password的验证时,是否还会评估firstName控件?我不想!
Pascal

16

扩大马修丹尼尔的答案,因为它并不完全正确。这是一些示例代码,显示了如何正确地将验证器分配给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


如果我们添加单选按钮和复选框该如何获取这两个值呢?
Pardeep Jain

2
ControlGroup被删除,以FormGroup供任何对此感兴趣的人使用。Docs and Learn Angular2示例
sofly

2

这是我能够想到的另一种选择,它不依赖于整个或子ControlGroup而是直接与每个或子捆绑在一起Control

我遇到的问题是相互依赖的控件没有层次结构在一起,因此无法创建ControlGroup。另外,我的CSS设置为每个控件将利用现有的角度类来确定是否显示错误样式,这在处理组验证而不是控件特定的验证时更加复杂。尝试确定单个控件是否有效是不可能的,因为验证是与控件组而不是每个控件相关的。

在我的情况下,我想要一个选择框的值来确定是否需要另一个字段。

这是使用组件上的“表单生成器”构建的。对于选择模型,不是将其直接绑定到请求对象的值,而是将其绑定到获取/设置函数,这些函数将允许我处理控件的“更改时”事件。然后,我将能够根据选择控件的新值来手动设置另一个控件的验证。

这是相关的视图部分:

<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 设置为null。

更新:还有其他方法可以捕获模型更改,例如(ngModelChange)=changeFunctionName($event)或通过使用来控制值更改this.form.controls["employee"].valueChanges.subscribe(data => ...))



1

也在寻找这个并且最终使用equalTo了ng2-validation包(https://www.npmjs.com/package/ng2-validation

这是一个示例:模板驱动:

<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>

1

这是我用来确保一个字段中的年龄大于或等于另一个字段中的年龄的版本。我也使用表单组,所以我使用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')
    });

0

我认为,目前最好的选择是创建一个表单组来保存控件。当您实例化控件时,请传入该函数以对其进行验证。例:

    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;
    });

只需根据您的域编辑邮件。



0

Angular 8在密码确认字段中进行验证的示例

仅供参考:如果在验证通过后更改了主要的“密码”字段,则不会更新passwordConfirm字段上的验证。但是,当用户在密码字段中输入密码时,您可以使密码确认字段无效

<input
  type="password"
  formControlName="password"
  (input)="registerForm.get('passwordConfirm').setErrors({'passwordMatches': true})"
/>

register.component.ts

import { PasswordConfirmValidator } from './password-confirm-validator';
export class RegisterComponent implements OnInit {
  registerForm: FormGroup = this.createRegisterForm({
    username: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [
      Validators.required,
      Validators.pattern('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$'),
      Validators.minLength(8)
    ]),
    passwordConfirm: new FormControl('', [
      Validators.required,
      PasswordConfirmValidator //custom validator
    ])
  });
}

密码确认验证器

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

export function PasswordConfirmValidator(control: AbstractControl) {
  if(void 0 === control){ return null; }
  if(
    void 0 !== control.parent &&
    void 0 !== control.parent.controls &&
    void 0 !== control.parent.controls['password'] &&
    control.parent.controls['password'].value === control.value
  ){
    return null;
  }
  return {passwordMatches: true};
}

register.component.html

{{registerForm.get('passwordConfirm').hasError('passwordMatches')}}

-2

我建议使用图书馆ng-form-rules。它是一个了不起的库,用于创建各种形式的表单,并具有与组件分离的验证逻辑,并且可以依赖于表单中其他区域的值更改。他们有出色的文档示例视频,展示了其大量功能。像这样进行验证很简单。

您可以查看他们的自述文件以获得一些高级信息和基本示例。


2
我不喜欢所有东西都有一个图书馆的想法……图书馆不是解决这个问题的方法。通常,仅使用另一个库就会产生新的问题,并且当Angular更新时,您还必须使这些内容保持最新。为什么不按照框架的意图使用角形?
纳丁

-3

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')});
  }

然后,您需要在constructorLike 方法中声明此方法。

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.