Home > Net >  How can I trigger validations for inputs in ReactiveForms?
How can I trigger validations for inputs in ReactiveForms?

Time:10-18

I have developed a form in Reactive Forms where the user has the possibility to set a new password. He enters the old and the new password and confirms the new password. I have considered the following cases:

  • New password must not contain the old password
  • New password and confirmation password are matched

The development works as well. Now I have one thing that is not nice. The validation is triggered only when I change something in the input field. But now I want to change the new password when I want to confirm the password, so I know if there is an error or not. I have read that this can be done with the function updateValueAndValidity. Do you know how to do this?

My Code:

// TS
changePasswordForm: FormGroup;
submitted = false;

ngOnInit() {
    // To initialize forms
    this.initChangePasswordForm();
  }

  // Creation of the changePasswordForm
  private initChangePasswordForm() {
    // General
    this.changePasswordForm = this.formBuilder.group({
      old_password: [null, Validators.pattern('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\\w\\s]).{8,}$')],
      new_password: [null, this.customComparePasswordValidator],
      confirm_password: [null, Validators.required]
    });
  }

 customComparePasswordValidator(control: FormControl) {
    const newPassword = control.value;
    if (newPassword && newPassword.length) {
      const oldPassword = control.parent.value.old_password;
      if (oldPassword && oldPassword.length && newPassword.toLowerCase().includes(oldPassword.toLowerCase())) {
        return { newPasswordIncludesOldPassword: true };
      }
      const pattern = new RegExp('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\\w\\s]).{8,}$');
      if (!pattern.test(newPassword)) {
        return { newPasswordInvalid: true };
      }
    } else {
      return { required: true };
    }
  }
// HTML
<input matInput type="password" placeholder="aktuelles Passwort" formControlName="old_password" required>
 <p *ngIf="changePasswordForm.get('old_password').invalid && (changePasswordForm.get('old_password').dirty || changePasswordForm.get('old_password').touched)">
            <mat-error class="custom-validation-error" *ngIf="changePasswordForm.get('old_password').hasError('required')">aktuelles Passwort eingeben</mat-error>
            <mat-error class="custom-validation-error" *ngIf="changePasswordForm.get('old_password').hasError('pattern')">8 oder mehr Zeichen mit einer Mischung aus Groß- und Kleinbuchstaben, Ziffern und Symbolen verwenden</mat-error>
          </p>

<input matInput type="password" placeholder="neues Passwort" formControlName="new_password" required>
<p *ngIf="changePasswordForm.get('new_password').invalid && (changePasswordForm.get('new_password').dirty || changePasswordForm.get('new_password').touched)">
            <mat-error  *ngIf="changePasswordForm.get('new_password').hasError('required')">neues Passwort eingeben</mat-error>
            <mat-error  *ngIf="changePasswordForm.get('new_password').hasError('newPasswordInvalid')">8 oder mehr Zeichen mit einer Mischung aus Groß- und Kleinbuchstaben, Ziffern und Symbolen verwenden</mat-error>
            <mat-error  *ngIf="changePasswordForm.get('new_password').hasError('newPasswordIncludesOldPassword')">Neues und altes Passwort dürfen nicht gleich sein</mat-error>
          </p>

 <input matInput type="password" placeholder="Passwort bestätigen" formControlName="confirm_password" appConfirmEqualValidator="new_password" required>
 <p *ngIf="changePasswordForm.get('confirm_password').invalid && (changePasswordForm.get('confirm_password').dirty || changePasswordForm.get('confirm_password').touched)">
            <mat-error  *ngIf="changePasswordForm.get('confirm_password').hasError('required')">Passwort erneut eingeben</mat-error>
            <mat-error  *ngIf="changePasswordForm.get('confirm_password').hasError('notEqual') && !changePasswordForm.get('confirm_password').hasError('required')">Passwort stimmt nicht überein</mat-error>
          </p>

My StackBlitz: https://stackblitz.com/edit/example-angular-material-reactive-form-sr1keu?file=app/app.component.html

CodePudding user response:

As you say, a validator only "check" when you change the FormControl. So you can "force" to validity one control when change the second. So, e.g. you simply can do, in the FormControl "new_password" write

<input matInput ... formControlName="new_password"
     (input)="changePasswordForm.get('confirm_password').updateValueAndValidity()"  />

(idem with the other FormControl)

You can also subscribe to valueChanges after create the form -don't forget unsubscribe-

  private initChangePasswordForm() {
    // After create the formGroup
    this.changePasswordForm = this.formBuilder.group({..}

    //subscribe to changes "new_password"
    this.changePasswordForm.get('new_password').valueChanges
       .subscribe(_=>{
            this.changePasswordForm.get('confirm_password').updateValueAndValidity()     
    })

Another approach is create a customValidator over the whole form. As you use material, You need use setErrors. The validator can be like

  validatorRepeatPassword(form: FormGroup) {
    const errorOldPassword = {};
    const errorNewPassword = {};
    const errorConfirmPassword = {};
    const pattern = new RegExp(
      '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\\w\\s]).{8,}$'
    );
    if (!form.value.old_password) errorOldPassword['required'] = true;
    else {
      if (!pattern.test(form.value.old_password))
        errorOldPassword['pattern'] =
          '8 or more characters with a mixture of upper and lower case letters,use numbers and symbols';
    }
    if (!form.value.new_password) errorNewPassword['required'] = true;
    else {
      if (!pattern.test(form.value.new_password))
        errorNewPassword['pattern'] =
          '8 or more characters with a mixture of upper and lower case letters,use numbers and symbols';
      if (
        form.value.old_password &&
        form.value.new_password
          .toLowerCase()
          .includes(form.value.old_password.toLowerCase())
      )
        errorNewPassword['newPasswordIncludesOldPassword'] = true;
    }
    if (!form.value.confirm_password) errorConfirmPassword['required'] = true;
    else {
      if (
        form.value.new_password &&
        form.value.confirm_password != form.value.new_password
      )
        errorConfirmPassword['notEqual'] = true;
    }
    form.controls.old_password.setErrors(
      Object.keys(errorOldPassword).length ? errorOldPassword : null
    );
    form.controls.new_password.setErrors(
      Object.keys(errorNewPassword).length ? errorNewPassword : null
    );
    form.controls.confirm_password.setErrors(
      Object.keys(errorConfirmPassword).length ? errorConfirmPassword : null
    );
    return {
      ...errorOldPassword,
      ...errorNewPassword,
      ...errorConfirmPassword,
    };
  }

And use

this.changePasswordForm = this.formBuilder.group(
  {
    old_password: null,
    new_password: null,
    confirm_password: null,
  },
  { validator: this.validatorRepeatPassword }
);

The last aproach (I only make with new_password and confirm password is make the validator with the two controls. A function like

  matchValidator(field: string,not:boolean=false) {
    return (control: AbstractControl) => {
      const parent = control.parent;
      const controlCompare = parent ? parent.get(field) : null;
      if (controlCompare && controlCompare.value && control.value) {
        const invalid = controlCompare.value != control.value;
        if (invalid!=controlCompare.invalid)
        {
          setTimeout(()=>{
            controlCompare.updateValueAndValidity();

          })
        }

        return invalid && !not ? { match: 'no match' } : null;
      }
    };
  }

Allow us write, e.g.

this.changePasswordForm = this.formBuilder.group(
  {
    old_password: null,
    new_password: [null,this.matchValidator('confirm_password',true)],
    confirm_password: [null,this.matchValidator('confirm_password',true)]
  }
);

See that you use a function to pass parameters -that should be fixed-

  • Related