【问题标题】:Angular custom validation: ensure form control is invalid when the form group has validation errorsAngular 自定义验证:当表单组出现验证错误时,确保表单控件无效
【发布时间】:2020-10-22 14:52:03
【问题描述】:

我正在研究 Angular 的反应形式。我已经为PasswordConfirm password 设置了字段,如下所示。我正在使用自定义验证来确保 Password 字段与 Confirm password 字段匹配。问题是,尽管在控制台日志记录中,我可以看到表单error 属性将mismatch 设置为trueconfirm password 字段仍然有效,因此表单仍然有效。

  1. 当表单在errors 属性中设置了mismatch 属性时,如何确保Confirm Password 字段也无效?
  2. 如果我将passwordMatchValidator 附加到confirmPassword(在formcontrol 定义中)而不是在formGroup 级别中设置,那么如何设置我的验证?

我的组件模板

    <form [formGroup]="registerForm" (ngSubmit)="onRegister()">
        <div class="form-group">
        <input type="password"
        class="form-control" placeholder="Password" formControlName="password">
        <div class="invalid-feedback"  *ngIf="registerForm.get('password').hasError('required')">Password is required</div>
        <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('minlength')">Password must be at least 5 characters</div>
        <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('maxlength')">Password cannot exceed 8 characters</div>
      </div>
      <div class="form-group">
        <input type="password"
        class="form-control" placeholder="Confirm Password" formControlName="confirmPassword">
        <div class="invalid-feedback" *ngIf="registerForm.get('confirmPassword').hasError('required')">Password is required</div>
        <div class="invalid-feedback" *ngIf="registerForm.hasError('mismatch')">Password must match</div>
      </div>
     </form>

Css 显示/隐藏错误反馈

.ng-valid{
    border: 1px solid green;
}
.ng-invalid.ng-touched + .invalid-feedback{
    display: block;
}

我的组件如下所示:

export class RegisterComponent implements OnInit {

 registerForm: FormGroup

  constructor(private fb: FormBuilder) { }
  
  ngOnInit(): void {
    this.registerForm = this.fb.group({
      "password": [null, [Validators.required, Validators.minLength(5), Validators.maxLength(8)]],
      "confirmPassword": [null, [Validators.required]]
    },{
      validators:this.passwordMatchValidator
    });
  }
  passwordMatchValidator(f:FormGroup):{[s:string]:boolean} {
    return f.controls['password'].value === f.controls['confirmPassword'].value ? null : { 'mismatch': true }
  }

  onRegister() {
    console.log(this.registerForm)
  }
}

我已经在 passwordMatchValidator 中设置了一个调试器并尝试了控制台日志记录,如下所示

【问题讨论】:

    标签: angular angular-reactive-forms angular-forms


    【解决方案1】:

    我使用下面的代码来验证密码。

    //In init method
    this.signUpForm = this._fb.group({
         
          password: ['',
            [Validators.required,
            Validators.minLength(8),
            Validators.pattern(this._config.PASSWORD_REGEX)
            ]
          ],
          confirmPassword: ['',
            Validators.required
          ]
        });
    this.signUpForm.get('password').valueChanges
          .subscribe(value => this.validatePassword(value));
        
    //method
    validatePassword(value: any): void {
        let ctrl = this.signUpForm.get('password');
        this.numberError = true;
        this.letterError = true;
        this.lengthError = true;
    
        if (/[a-z]/.test(value) && /[A-Z]/.test(value)) {
          this.letterError = false;
        }
    
        if (/[0-9]/.test(value)) {
          this.numberError = false;
        }
    
        if (value.length >= 8) {
          this.lengthError = false;
        }
      }
    //Method match text
    matchText(controlType: string): void {
        let control1 = null, control2 = null;
    
      if (controlType == 'password') {
          control1 = this.signUpForm.get('password');
          control2 = this.signUpForm.get('confirmPassword');
        }
    
    
        if (control1.value && control2.value) {
          if (control1.value != control2.value) {
            control2.setValidators(this.matchValidator());
          } else {
            control2.setValidators([Validators.required]);
          }
    
          control2.updateValueAndValidity();
        }
      }
    //match validator method
      matchValidator(): ValidatorFn {
        return (c: AbstractControl): { [key: string]: boolean } | null => {
          return { 'match': false };
        };
      }
    <mat-form-field>
              <input autocomplete="nope" formControlName="password" matInput placeholder="PASSWORD" [type]="hidePassword ? 'password' : 'text'"
                (change)="matchText('password')" maxlength="30">
              <mat-icon matSuffix (click)="hidePassword = !hidePassword">{{hidePassword ? 'visibility' : 'visibility_off'}}</mat-icon>
              <mat-hint align="start" class="signup_hint">
                <span>PASSWORD_MUST_HAVE...</span>
                <br>
                <span [ngClass]="{'hint-error': (((signUpForm.get('password').dirty ||
                signUpForm.get('password').touched) && signUpForm.get('password').invalid) &&
                signUpForm.get('password')?.errors?.required) || signUpForm.get('password')?.errors?.minlength || lengthError, '': !(((signUpForm.get('password').dirty ||
                signUpForm.get('password').touched) && signUpForm.get('password').invalid) &&
                signUpForm.get('password')?.errors?.required) && !signUpForm.get('password')?.errors?.minlength && !lengthError }">&nbsp; &nbsp;HINT_8_CHARACTERS</span>
                <br>
    
    
                <span [ngClass]="{'hint-error': signUpForm.get('password')?.errors?.pattern && letterError, '': !signUpForm.get('password')?.errors?.pattern && !letterError }">&nbsp; &nbsp; HINT_UPPER_AND_LOWER</span>
                <br>
                <span [ngClass]="{'hint-error': signUpForm.get('password')?.errors?.pattern && numberError, '': !signUpForm.get('password')?.errors?.pattern && !numberError }">&nbsp; &nbsp; HINT_AT_LEAST_ONE</span>
    
                <br/>
                 </mat-hint>
            </mat-form-field>
    
         
    
            <mat-form-field>
              <input autocomplete="nope" formControlName="confirmPassword" matInput placeholder="CONFIRM_PASSWORD" [type]="hidePassword ? 'password' : 'text'"
                (change)="matchText('password')" (paste)="$event.preventDefault()">
              <mat-icon matSuffix (click)="hidePassword = !hidePassword">{{hidePassword ? 'visibility' : 'visibility_off'}}</mat-icon>
              <mat-error *ngIf="(signUpForm.get('confirmPassword').dirty || signUpForm.get('confirmPassword').touched) &&
              signUpForm.get('confirmPassword').invalid">(signUpForm.get('confirmPassword')?.errors?.required ? 'ERR_REQUIRED_FIELD' : 'ERR_PASSWORD_MISMATCH')</mat-error>
            </mat-form-field>

    【讨论】:

    • Serendra,你没有“自定义验证器”,只有在密码匹配时才检查 onChange,所以你失去了验证器的潜力:(
    【解决方案2】:

    你可以使用类似的css:

    /*Only border green/red the "form controls" nor the div*/
    
    .form-control.ng-valid{
        border: 1px solid green;
    }
    .form-control.ng-invalid.ng-touched{
        border: 1px solid red;
    }
    
    .ng-invalid.ng-touched + .invalid-feedback{
        display: block;
    }
    /*Is invalid? the formControls valid and touched inside border red*/
    .ng-invalid .form-control.ng-valid.ng-touched 
    {
        border: 1px solid red!important;
    
    }
    

    看到 ng-invalid 是表单组本身(使用导航器工具 F12 来查看应用类的位置)

    【讨论】:

      猜你喜欢
      • 2021-09-05
      • 2018-02-04
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      • 2019-01-30
      • 2016-08-02
      • 1970-01-01
      • 2020-04-06
      相关资源
      最近更新 更多