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

时间:2015-08-03 13:30:25

标签: angular

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

e.g。如果客户的首选联系方式是通过电子邮件,则应该要求电子邮件字段。

感谢。

更新了示例代码...

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

}

15 个答案:

答案 0 :(得分:135)

要重申其他方法发布的方法,这就是我创建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。在这种情况下,我正在验证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/

答案 1 :(得分:42)

Dave's answer非常非常有帮助。但是,稍作修改可能会对某些人有所帮助。

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

答案 2 :(得分:24)

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

请参阅我对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

答案 3 :(得分:22)

我使用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上测试过。

答案 4 :(得分:15)

扩展matthewdaniel的答案,因为它不完全正确。以下是一些示例代码,演示如何将验证器正确分配给11-30 11:30:12.479 11680-11680/com.example.samarth.cameraimagecapture E/AndroidRuntime: FATAL EXCEPTION: main java.lang.RuntimeException: Unable to resume activity {com.example.samarth.cameraimagecapture/com.example.samarth.cameraimagecapture.MainActivity}: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=null} to activity {com.example.samarth.cameraimagecapture/com.example.samarth.cameraimagecapture.MainActivity}: java.lang.NullPointerException at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2458) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2486) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2000) at android.app.ActivityThread.access$600(ActivityThread.java:128) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4517) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=null} to activity {com.example.samarth.cameraimagecapture/com.example.samarth.cameraimagecapture.MainActivity}: java.lang.NullPointerException at android.app.ActivityThread.deliverResults(ActivityThread.java:2997) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2445) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2486)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2000)  at android.app.ActivityThread.access$600(ActivityThread.java:128)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)  at android.os.Handler.dispatchMessage(Handler.java:99)  at android.os.Looper.loop(Looper.java:137)  at android.app.ActivityThread.main(ActivityThread.java:4517)  at java.lang.reflect.Method.invokeNative(Native Method)  at java.lang.reflect.Method.invoke(Method.java:511)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)  at dalvik.system.NativeStart.main(Native Method)  Caused by: java.lang.NullPointerException at java.io.File.fixSlashes(File.java:185) at java.io.File.<init>(File.java:134) at com.example.samarth.cameraimagecapture.MainActivity.onActivityResult(MainActivity.java:175) at android.app.Activity.dispatchActivityResult(Activity.java:4654) at android.app.ActivityThread.deliverResults(ActivityThread.java:2993) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2445)  at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2486)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2000)  at android.app.ActivityThread.access$600(ActivityThread.java:128)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)  at android.os.Handler.dispatchMessage(Handler.java:99)  at android.os.Looper.loop(Looper.java:137)  at android.app.ActivityThread.main(ActivityThread.java:4517)  at java.lang.reflect.Method.invokeNative(Native Method)  at java.lang.reflect.Method.invoke(Method.java:511)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)  at dalvik.system.NativeStart.main(Native Method) 

ControlGroup

以下是一个有效的例子:http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview

答案 5 :(得分:13)

大量挖掘角度来源,但我找到了更好的方法。

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 :(得分:2)

这是我能够提出的另一个选项,它不依赖于整个或子ControlGroup,而是直接绑定到每个Control

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

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

这是使用组件上的表单构建器构建的。对于select模型而不是直接将其绑定到请求对象的值,我将它绑定到get / set函数,这将允许我处理&#34; on change&#34;控制事件。然后,我将能够根据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,则可以设置为(ngModelChange)=changeFunctionName($event)任何与控制相关的验证。

更新:还有其他方法可以捕获模型更改,例如this.form.controls["employee"].valueChanges.subscribe(data => ...))或使用list1订阅控件值更改

答案 7 :(得分:2)

我尝试了大部分这些答案,但没有一个对我有用。我在这里找到了一个有效的例子 https://scotch.io/@ibrahimalsurkhi/match-password-validation-with-angular-2

答案 8 :(得分:1)

正在寻找这个并最终使用ng2-validation软件包中的equalTo 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>

答案 9 :(得分: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')
    });

答案 10 :(得分:0)

我认为现在最好的办法是创建一个表单组来保存您的控件。在函数中实例化Control传递以验证它时。例如:

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

只需根据您的域名修改邮件。

答案 11 :(得分:0)

Louis Cruz's answer对我非常有帮助。

要完成添加,否则setErrors重置: return passwordConfirmationInput.setErrors(null);

一切正常!

谢谢你,

此致

TGA

答案 12 :(得分: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
    ])
  });
}

password-confirm-validator.ts

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

答案 13 :(得分:-1)

我建议使用库ng-form-rules。这是一个了不起的库,用于创建各种形式的表单,并具有与组件分离的验证逻辑,并且可以依赖于表单中其他区域的值更改。它们具有great documentationexamplesvideo that shows a bunch of its functionality。像这样进行验证很简单。

您可以check out their README获取一些高级信息和基本示例。

答案 14 :(得分:-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')});
  }

然后你需要在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>