Custom Validator on reactive form for password and confirm password matching getting undefined parameters into Angular 4

可紊 提交于 2019-12-17 08:48:13

问题


I'm trying to implement a custom validator to check if the password and password confirm are equal. The problem is that the validator is getting undefined password and confirmedPassword parameters. How do I make this work. The function works cause if I change the condition to === instead of !== it throws the error correctly when the fields are the same. Does anyone know which is the error here?

signup.component.html

<div class="col-md-7 col-md-offset-1 col-sm-7">
  <div class="block">
    <div class="well">
        <form (onSubmit)="onSubmit()" [formGroup]="signUpForm">
          <div class="form-group">
            <label for="username" class="control-label">Nombre de usuario:</label>
            <input type="text" class="form-control" formControlName="username"  title="Please enter your username" placeholder="username">
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('required') && signUpForm.get('username').touched">El nombre de usuario es obligatorio</p>
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('minlength') && signUpForm.get('username').touched">El nombre de usuario debe tener al menos 6 caracteres</p>
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('maxlength') && signUpForm.get('username').touched">El nombre de usuario debe tener menos de 15 caracteres</p>
          </div>
          <div class="form-group">
            <label for="email" class="control-label">E-mail:</label>
            <input class="form-control" formControlName="email" title="Please enter your email" placeholder="example@gmail.com">
            <p class="help-block" *ngIf="signUpForm.get('email').hasError('required') && signUpForm.get('email').touched">La dirección de email es obligatoria</p>
            <p class="help-block" *ngIf="signUpForm.get('email').hasError('email') && signUpForm.get('email').touched">Debe ingresar una dirección de correo válida</p>
          </div>
          <div class="form-group">
            <label for="password" class="control-label">Contraseña:</label>
            <input type="password" class="form-control" formControlName="password" title="Please enter your password" [(ngModel)]="password">
            <p class="help-block" *ngIf="signUpForm.get('password').hasError('required') && signUpForm.get('password').touched">Debe ingresar una contraseña</p>
          </div>
          <div class="form-group">
            <label for="confirmedPassword" class="control-label">Confirmar Contraseña:</label>
            <input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password" [(ngModel)]="confirmedPassword">
            <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('required') && signUpForm.get('confirmedPassword').touched">La confirmación de contraseña no puede estar vacía</p>
            <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('passwordMismatch') && signUpForm.get('confirmedPassword').touched">Las contraseñas no coinciden</p>
          </div>
          <button type="submit" class="btn btn-success" [disabled]="!signUpForm.valid">Registrarse</button>
          <a routerLink="/signin" class="btn btn-default" style="">Ya tenes usuario? Logueate</a> {{ creationMessage }}
        </form>

      </div>

  </div>
</div>

signup.component.ts

import { Component, OnInit, ViewChild, Input } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { CustomValidators } from '../../shared/custom-validators';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.sass']
})
export class SignupComponent implements OnInit {

  signUpForm: FormGroup;
  user = {
    username: '',
    email: '',
    password: ''
  };
  submitted = false;
  @Input() password='';
  @Input() confirmedPassword='';

  constructor() { }

  ngOnInit() {

    this.signUpForm = new FormGroup({
      'username': new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
      'email': new FormControl(null, [Validators.required, Validators.email, Validators.minLength(5)]),
      'password': new FormControl(null, [Validators.required]),
      'confirmedPassword': new FormControl(null, [Validators.required, CustomValidators.passwordsMatch(this.password,this.confirmedPassword).bind(this)])
    });
  }

  onSubmit() {
    if (this.signUpForm.valid) {
      console.log(this.signUpForm.value);
    }
  }

}

custom-validators.ts

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

    export class CustomValidators{

    public static passwordsMatch(password: string, confirmedPassword: string) {

     return (control: FormControl) : { [s: string]: boolean } =>{
       //getting undefined values for both variables
       console.log(password,confirmedPassword);
        //if I change this condition to === it throws the error if the 
//  two fields are the same, so this part works
        if (password !== confirmedPassword) {
          return { 'passwordMismatch': true }
        } else {
          //it always gets here no matter what
          return null;
        }
    }
      }


    }

回答1:


import {AbstractControl, FormBuilder, FormGroup, Validators} from

set your password input into the group and no need to use "ngModel".

<div class="form-group row" formGroupName="passwords">
  <div class="form-group">
     <label for="password" class="control-label">Contraseña:</label>
     <input type="password" class="form-control" formControlName="password" title="Please enter your password">
     <p class="help-block" *ngIf="signUpForm.get('password').hasError('required') && signUpForm.get('password').touched">Debe ingresar una contraseña</p>
  </div>
  <div class="form-group">
     <label for="confirmedPassword" class="control-label">Confirmar Contraseña:</label>
     <input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password">
     <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('required') && signUpForm.get('confirmedPassword').touched">Password must be required</p>
     <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('passwordMismatch') && signUpForm.get('confirmedPassword').touched">password does not match</p>
  </div>

     buildForm(): void {
            this.userForm = this.formBuilder.group({
                passwords: this.formBuilder.group({
                    password: ['', [Validators.required]],
                    confirm_password: ['', [Validators.required]],
                }, {validator: this.passwordConfirming}),

            });
        }

add this custom function for validate password and confirm password

  passwordConfirming(c: AbstractControl): { invalid: boolean } {
    if (c.get('password').value !== c.get('confirm_password').value) {
        return {invalid: true};
    }
}

Display error when password does not match

<div style='color:#ff7355' *ngIf="userForm.get(['passwords','password']).value != userForm.get(['passwords','confirm_password']).value && userForm.get(['passwords','confirm_password']).value != null">
  Password does not match</div>



回答2:


The issue is that you are mixing the reactive forms module with the input approach. This is causing you to get undefined when passing the values to the validator.

You don't need to bind to the ng-model when using the reactive forms. Instead, you should access the value of the fields from the Instance of FormGroup.

I do something like this in an app to validate the passwords match.

public Credentials: FormGroup;

ngOnInit() {
    this.Credentials = new FormGroup({});
    this.Credentials.addControl('Password', new FormControl('', [Validators.required]));
    this.Credentials.addControl('Confirmation', new FormControl(
        '', [Validators.compose(
            [Validators.required, this.validateAreEqual.bind(this)]
        )]
    ));
}

private validateAreEqual(fieldControl: FormControl) {
    return fieldControl.value === this.Credentials.get("Password").value ? null : {
        NotEqual: true
    };
}

Note that the validator expects a FormControl field as a parameter and it compares the value of the field to that of the value of the Password field of the Credentials FormGroup.

In the HTML make sure to remove the ng-model.

<input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password" >
<!-- AND -->
<input type="password" class="form-control" formControlName="password" title="Please enter your password">

Hope this helps!




回答3:


There are two types of validators: FormGroup validator and FormControl validator. To verify two passwords match, you have to add a FormGroup validator. Below is my example:

Note: this.fb is the injected FormBuilder

this.newAccountForm = this.fb.group(
  {
    newPassword: ['', [Validators.required, Validators.minLength(6)]],
    repeatNewPassword: ['', [Validators.required, Validators.minLength(6)]],
  }, 
  {validator: this.passwordMatchValidator}
);

passwordMatchValidator(frm: FormGroup) {
  return frm.controls['newPassword'].value === frm.controls['repeatNewPassword'].value ? null : {'mismatch': true};
}

and in the templeate:

<div class="invalid-feedback" *ngIf="newAccountForm.errors?.mismatch && (newAccountForm.controls['repeatNewPassword'].dirty || newAccountForm.controls['repeatNewPassword'].touched)">
  Passwords don't match.
</div>

The key point here is to add the FormGroup validator as the second parameter to the group method.




回答4:


Please update FormGroup code like below in Angular5

 this.signUpForm = new FormGroup({
      'username': new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
      'email': new FormControl(null, [Validators.required, Validators.email, Validators.minLength(5)]),
      'password': new FormControl(null, [Validators.required]),
      'confirmedPassword': new FormControl(null, [Validators.required])
    }, this.pwdMatchValidator);

Add pwdMatchValidator function in your component

pwdMatchValidator(frm: FormGroup) {
    return frm.get('password').value === frm.get('confirmedPassword').value
       ? null : {'mismatch': true};
 }

Add validation message in your template

<span *ngIf="confirmedPassword.errors || signUpForm .errors?.mismatch">
              Password doesn't match
            </span>

Please find the below angular material working component.

Component Templete Code password.component.html

 <form class="cahnge-pwd-form" (ngSubmit)="onSubmit()" name="passwordForm" [formGroup]="passwordForm" #formDir="ngForm">
      <div fxLayout='column'>
    <mat-form-field>
      <input matInput name="password" placeholder="Password" [type]="hide ? 'text' : 'password'" formControlName="password" required>
      <mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
      <mat-error *ngIf="password.invalid && (password.dirty || password.touched || isSubmit)">
        <span *ngIf="password.errors.required">
          Please enter a Password.
        </span>
        <span *ngIf="password.errors.maxlength">
          Please enter a Email no more than 16 characters.
        </span>
        <span *ngIf="password.errors.minlength">
          Please enter a password at least 6 characters.
        </span>
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput name="password" placeholder="Confirm Password" [type]="confirm_hide ? 'text' : 'password'" formControlName="confirm_password"
        required>
      <mat-icon matSuffix (click)="confirm_hide = !confirm_hide">{{confirm_hide ? 'visibility_off' : 'visibility'}}</mat-icon>
      <mat-error *ngIf="(confirm_password.invalid && (confirm_password.dirty || confirm_password.touched || isSubmit) || passwordForm.errors?.mismatch)">
        <span *ngIf="confirm_password.errors || passwordForm.errors?.mismatch">
          Password doesn't match
        </span>
      </mat-error>
    </mat-form-field>
    <div fxLayout='row' fxLayoutGap="10px">
      <button type="submit" mat-raised-button color="primary">Submit</button>
      <button type="button" (click)="formDir.resetForm(passwordForm)" mat-raised-button color="warn">Cancel</button>
    </div>
  </div>
</form>

Component Code : password.component.ts

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { PasswordService } from './password.service';
import { PasswordValidation  } from './confirm';

@Component({
  selector: 'app-password',
  templateUrl: './password.component.html',
  styleUrls: ['./password.component.css']
})
export class PasswordComponent implements OnInit {
  passwordForm: FormGroup;
  isSubmit: boolean;
  constructor(private router: Router, private passwordService: PasswordService, private toastrService: ToastrService, private route: ActivatedRoute) { }
  ngOnInit() {
    this.passwordForm = new FormGroup({
      'password': new FormControl('', [
        Validators.required,
        Validators.minLength(6),
        Validators.maxLength(16),
      ]),
      'confirm_password': new FormControl('', [
         Validators.required,
        Validators.minLength(6),
        Validators.maxLength(16),
      ]),
    }, this.pwdMatchValidator);

  }
 pwdMatchValidator(frm: FormGroup) {
    return frm.get('password').value === frm.get('confirm_password').value
       ? null : {'mismatch': true};
 }

  get password() { return this.passwordForm.get('password'); }
  get confirm_password() { return this.passwordForm.get('confirm_password'); }

  onSubmit(formvalue):boolean {
    this.isSubmit = true;
    if (this.passwordForm.invalid) {
      return false;
    } else {
      this.passwordService.updatePassword(this.passwordForm.value)
      .subscribe((res) => {
        if (res.status == 'success') {
          this.toastrService.success(res.msg);
          this.router.navigate(['/change-password']);
        }
      })
      return true;
    }

  }

}



回答5:


I would do the same as Shailesh Ladumor but adding the following line before returning in the validation function:

c.get('confirm_password').setErrors({'noMatch': true});

So that the validation function looks like this:

passwordConfirming(c: AbstractControl): { invalid: boolean } {
    if (c.get('password').value !== c.get('confirm_password').value) {
        c.get('confirm_password').setErrors({'noMatch': true});
        return {invalid: true};
    }
}

This will not only set the hole userForm as an invalid form group, but it will also set confirm_password as an invalid form control.

With this you can later call the following function in your template:

public getPasswordConfirmationErrorMessage() {
if (this.userForm.get('confirm_password').hasError('required')) {
  return 'You must retype your password';
} else if (this.userForm.get('confirm_password').hasError('noMatch')) {
  return 'Passwords do not match';
} else {
  return '';
}

}




回答6:


When you need to validate on more than one field, and you wish to declare validator at form creation time, FormGroup validator must be used. The main issue with form validator is that it attaches error to form and not to validating control, which leeds to some inconsistents in template. Here is reusable form validator wich attaches error to both form and control

// in validators.ts file

export function controlsEqual(
  controlName: string,
  equalToName: string,
  errorKey: string = controlName // here you can customize validation error key 
) {

  return (form: FormGroup) => {
    const control = form.get(controlName);

    if (control.value !== form.get(equalToName).value) {
      control.setErrors({ [errorKey]: true });
      return {
        [errorKey]: true
      }
    } else {
      control.setErrors(null);
      return null
    }
  }
}

// then you can use it like
  ngOnInit() {
    this.vmForm = this.fb.group({
      username: ['', [Validators.required, Validators.email]],
      password: ['', [
        Validators.required,
        Validators.pattern('[\\w\\d]+'),
        Validators.minLength(8)]],
      confirm: [''], // no need for any validators here
    }, { 
        // here we attach our form validator
        validators: controlsEqual('confirm', 'password')
      });
  }




回答7:


When you're creating the validator, you're passing in the values of password and confirmedPassword, but changes in those values will not be reflected in the validator.

The two options I see are:

  1. define your validator on the FormGroup, and look up the values from the two controls you want to compare; or
  2. since you're binding to this already, use this.password and this.confirmedPassword in your validator.



回答8:


const form = new FormGroup({
  password: new FormControl('', Validators.minLength(2)),
  passwordConfirm: new FormControl('', Validators.minLength(2)),
}, passwordMatchValidator);

function passwordMatchValidator(g: FormGroup) {
   return g.get('password').value === g.get('passwordCenter code hereonfirm').value
      ? null : {'mismatch': true};
}

Have to create a new service and you can call the created service while you want to validate the password and confirmation fields.



来源:https://stackoverflow.com/questions/44449673/custom-validator-on-reactive-form-for-password-and-confirm-password-matching-get

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!