angularformsangular-reactive-formsreset-password

Confirm password validation in angular


I have a reset-password form with 2 input fields:

  1. New Password
  2. New Password (confirmation)

I had to create a validation where "New Password (confirmation) needed to match "New Password" and I made it. When you type in a wrong password in "New Password (confirmation) field, it gives an error and wont let you submit the form until it matches the password in field "New Password".But if I go back and change "New Password" field, "New Password (confirmation) field shows it like they still match even though they arent anymore...So my question is: how can i fix this issue?

Any advice is appreciated

ts file

import { AbstractControl, FormBuilder, FormControl, Validators } from '@angular/forms';
import { AppConfig } from 'src/app/_common/configs/app.config';
import { Component } from '@angular/core';

@Component({
  selector: 'app-reset-password',
  templateUrl: './reset-password.component.html',
  styleUrls: ['./reset-password.component.scss'],
})
export class ResetPasswordComponent {
  passwordsMatching = false;
  isConfirmPasswordDirty = false;
  confirmPasswordClass = 'form-control';
  appConfig = AppConfig;
  newPassword = new FormControl(null, [
    (c: AbstractControl) => Validators.required(c),
    Validators.pattern('^((?!.*[s])(?=.*[A-Z])(?=.*d).{8,99})'),
  ]);
  confirmPassword = new FormControl(null, [
    (c: AbstractControl) => Validators.required(c),
    Validators.pattern('^((?!.*[s])(?=.*[A-Z])(?=.*d).{8,99})'),
  ]);

  resetPasswordForm = this.formBuilder.group({
    newPassword: this.newPassword,
    confirmPassword: this.confirmPassword,
  });

  constructor(private formBuilder: FormBuilder) {}

  onSubmit(): void {
    if (!this.resetPasswordForm?.valid) {
      return;
    }
  }

  checkPasswords(pw: string, cpw: string) {
    this.isConfirmPasswordDirty = true;
    if (pw == cpw) {
      this.passwordsMatching = true;
      this.confirmPasswordClass = 'form-control is-valid';
    } else {
      this.passwordsMatching = false;
      this.confirmPasswordClass = 'form-control is-invalid';
    }
  }
}

**
HTML**

<section class="h-100">
  <div class="container h-100"> 
    <div class="card reset-password-form-card">
      <div class="card-body p-5">
        <h4 class="card-title fw-bold mb-4">Reset password</h4>
        <form class="form" [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()">
          <div class="mb-3">
            <label for="newPassword">New password: </label>
            <input id="newPassword" type="password" 
            class="form-control" formControlName="newPassword" #pw>
            <div *ngIf="newPassword.invalid && (newPassword.dirty || newPassword.touched)"
            class="form-text text-danger">
            <div *ngIf="newPassword.errors?.['required']">
              Field is required
            </div>
            <div *ngIf="newPassword.errors?.['pattern']">
              Password must contain at least one number, one uppercase and a lowercase letter 
              and at least 8 characters<br>Password cannot contain whitespace
            </div>
          </div>
        </div>
        <div class="mb-3">
          <label for="confirmPassword">New password (confirmation):</label>
          <input id="confirmPassword" type="password" class="form-control"
          [ngClass]='confirmPasswordClass' formControlName="confirmPassword"
          #cpw (keyup)='checkPasswords(pw.value, cpw.value)'>
          <div *ngIf="confirmPassword.invalid && (confirmPassword.dirty || confirmPassword.touched)"
          class="form-text text-danger">
        <div *ngIf="confirmPassword.errors?.['required']">
          Field is required        
        </div>
        <div *ngIf="confirmPassword.errors?.['pattern']">
          Password must contain at least one number, one uppercase and a lowercase letter 
          and at least 8 characters<br>Password cannot contain whitespace
        </div>
        <div *ngIf='!passwordsMatching && isConfirmPasswordDirty'>
          Passwords did not match
      </div>
      </div>
    </div>
    <div class="d-flex align-items-center">
      <button [disabled]="!resetPasswordForm.valid" type="submit" class="btn btn-dark col-5 mx-auto">
        Reset password
      </button>
      <button type="button" class="btn btn-light col-5 mx-auto" appBackButton>Back</button>
    </div>
   </form>
      </div>
    </div> 
  </div>
</section>

Solution

  • I have done some changes on your code so please check

    ts file

    export class AppComponent {
      name = 'Angular ' + VERSION.major;
      passwordsMatching = false;
      isConfirmPasswordDirty = false;
      confirmPasswordClass = 'form-control';
      newPassword = new FormControl(null, [
        (c: AbstractControl) => Validators.required(c),
        Validators.pattern(
          /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*#?&^_-]).{8,}/
        ),
      ]);
      confirmPassword = new FormControl(null, [
        (c: AbstractControl) => Validators.required(c),
        Validators.pattern(
          /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*#?&^_-]).{8,}/
        ),
      ]);
    
      resetPasswordForm = this.formBuilder.group(
        {
          newPassword: this.newPassword,
          confirmPassword: this.confirmPassword,
        },
        {
          validator: this.ConfirmedValidator('newPassword', 'confirmPassword'),
        }
      );
    
      constructor(private formBuilder: FormBuilder) {}
    
      onSubmit(): void {
        console.log(this.resetPasswordForm);
        if (!this.resetPasswordForm?.valid) {
          return;
        }
      }
    
      ConfirmedValidator(controlName: string, matchingControlName: string) {
        return (formGroup: FormGroup) => {
          const control = formGroup.controls[controlName];
          const matchingControl = formGroup.controls[matchingControlName];
          if (
            matchingControl.errors &&
            !matchingControl.errors.confirmedValidator
          ) {
            return;
          }
          if (control.value !== matchingControl.value) {
            matchingControl.setErrors({ confirmedValidator: true });
          } else {
            matchingControl.setErrors(null);
          }
        };
      }
    }
    

    HTML

    <section class="h-100">
      <div class="container h-100">
        <div class="card reset-password-form-card">
          <div class="card-body p-5">
            <h4 class="card-title fw-bold mb-4">Reset password</h4>
            <form
              class="form"
              [formGroup]="resetPasswordForm"
              (ngSubmit)="onSubmit()"
            >
              <div class="mb-3">
                <label for="newPassword">New password: </label>
                <input
                  id="newPassword"
                  type="password"
                  class="form-control"
                  formControlName="newPassword"
                  #pw
                />
                <div
                  *ngIf="
                    newPassword.invalid &&
                    (newPassword.dirty || newPassword.touched)
                  "
                  class="form-text text-danger"
                >
                  <div *ngIf="newPassword.errors?.['required']">
                    Field is required
                  </div>
                  <div *ngIf="newPassword.errors?.['pattern']">
                    Password must contain at least one number, one uppercase and a
                    lowercase letter and at least 8 characters<br />Password cannot
                    contain whitespace
                  </div>
                </div>
              </div>
              <div class="mb-3">
                <label for="confirmPassword">New password (confirmation):</label>
                <input
                  id="confirmPassword"
                  type="password"
                  class="form-control"
                  [ngClass]="confirmPasswordClass"
                  formControlName="confirmPassword"
                  #cpw
                />
                <div
                  *ngIf="
                    confirmPassword.invalid &&
                    (confirmPassword.dirty || confirmPassword.touched)
                  "
                  class="form-text text-danger"
                >
                  <div *ngIf="confirmPassword.errors?.['required']">
                    Field is required
                  </div>
                  <div *ngIf="confirmPassword.errors?.['pattern']">
                    Password must contain at least one number, one uppercase and a
                    lowercase letter and at least 8 characters<br />Password cannot
                    contain whitespace
                  </div>
                  <div *ngIf="confirmPassword.errors?.['confirmedValidator']">
                    Passwords did not match
                  </div>
                </div>
              </div>
              <div class="d-flex align-items-center">
                <button
                  type="submit"
                  [disabled]="resetPasswordForm.invalid"
                  class="btn btn-dark col-5 mx-auto"
                >
                  Reset password
                </button>
                <button
                  type="button"
                  class="btn btn-light col-5 mx-auto"
                  appBackButton
                >
                  Back
                </button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </section>
    

    And here is the working demo of it:- https://stackblitz.com/edit/angular-ivy-pjdyva?file=src%2Fapp%2Fapp.component.ts