Angular 2 Match password in a form

Viewed 5159

I know this question has been asked other times, in similar forms, but I have a little different situation; let me explain:

I have a reset password form, with 3 fields (old password, new password, confirm password). Of course I need to check if the submitted passowrd is equal the submittedw confirmpassword and I want to do it before clicking submit. I defined the form as follows (in the component.ts)

this.form = fb.group({
  // define your control in you form
  oldpassword: ['', Validators.required],
  password: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(10)]],
  confirmPassword: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(10)]]
}, {
  validator: PasswordValidation.MatchPassword // your validation method
});

export class PasswordValidation {

static MatchPassword(AC: AbstractControl) {
    const password = AC.get('password').value; // to get value in input tag
    const confirmPassword = AC.get('confirmPassword').value; // to get value in input tag
    if (password !== confirmPassword) {
      AC.get('confirmPassword').setErrors({MatchPassword: true})
    } else {
        return null
    }
}
}  

Here is the problem, the form is not valid if I change the "confirm password field" before compiling the "new password" one. In other words, if I respect vertical o9rder of the fields, all is OK, but if I compile the fields in different order, then the form is still invalid.

here is the html code : (as you can see, it is a bit confusing because I have other UX constraint ehich corresponds to user possible interactions)

<form [formGroup]="form" novalidate (ngSubmit)="modal.open()">
        <div class="form-group">
          <label for="oldpassword">Inserisci vecchia password</label>
          <input type="password" id="oldpassword" class="form-control" formControlName="oldpassword">
          <div
            *ngIf="form.controls['oldpassword'].hasError('required') && form.controls['oldpassword'].touched"
            class="alert alert-danger">
            Campo obbligatorio
          </div>
        </div>
        <div class="form-group">
          <label for="password">Inserisci nuova password</label>
          <input type="password" id="password" class="form-control" formControlName="password">
          <div *ngIf="form.controls['password'].hasError('required') && form.controls['password'].touched"
               class="alert alert-danger">
            Campo obbligatorio
          </div>
          <div *ngIf="form.controls['password'].hasError('minlength') && form.controls['password'].touched"
               class="alert alert-danger">
            Lunghezza minima: 4 caratteri.
          </div>
        </div>
        <div class="form-group">
          <label for="confirmPassword">Ripeti nuova password</label>
          <input type="password" class="form-control" id="confirmPassword" formControlName="confirmPassword">
          <div class="alert alert-danger"
               *ngIf="form.errors?.MatchPassword || (form.controls['confirmPassword'].hasError('required') && form.controls['confirmPassword'].touched)">
            Le password non corrispondono
          </div>
        </div>
        <div *ngIf="!this.state.controlloIn; else elseBlock">
          <button type="submit" class="btn btn-primary btn-update" [disabled]="!form.valid || this.controlloInvia">
            Invia Richiesta
          </button>
        </div>
        <ng-template #elseBlock>
          <button type="submit" class="btn btn-primary btn-update" [disabled]="true">
            Invia Richiesta
          </button>
        </ng-template>
      </form>

I'm not an angular expert, so if you please could suggest me other best practice I'll be really happy. thanks to all

4 Answers

You are setting your errors directly on the confirmation field. If you don't touch that field anymore, errors are set upon it and will never get removed. Angular run validators against a field only if it has been edited because you can legitimately think that if a field is marked as error and hasn't been edited, it is still in error state.

I would recommend you to put this error directly upon the FormGroup instead :

static MatchPassword(AC: AbstractControl) {
    const password = AC.get('password').value; // to get value in input tag
    const confirmPassword = AC.get('confirmPassword').value; // to get value in input tag
    if (password !== confirmPassword) {
      return {MatchPassword: true};
    } else {
        return null
    }
}
<div class="alert alert-danger"
               *ngIf="form.errors?.MatchPassword || (form.controls['confirmPassword'].hasError('required') && form.controls['confirmPassword'].touched)">
            Le password non corrispondono
          </div>
this.profileForm = this.formBuilder.group({
  password: [this.profile.password, [
    Validators.required,
    Validators.minLength(4)]
  ],
  passwordCheck: [this.profile.password, [
    Validators.required,
    Validators.minLength(4)
  ]],
  oldPassword: [this.profile.oldPassword, [
    Validators.required]],
}, { validator: matchingPasswordsValidator('password', 'passwordCheck') });

this is a form i use for my password change. It uses a seperate validator that doesn't validate a field it validates the entire form.

export function matchingPasswordsValidator(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
      };
    }
  }
}

@Directive({
  selector: '[appMatchingPasswordsValidator]',
  providers: [{
    provide: NG_VALIDATORS, useExisting: MatchingPasswordsValidatorDirective, multi: true
  }]
})
export class MatchingPasswordsValidatorDirective {

  constructor() { }

  validate(c: AbstractControl) {
    matchingPasswordsValidator("test", "test");
  }
}

Then this is the directive used to match the passwords. or any 2 keys you give it present in the form.

I know this is a late answer to your question but wanted to show a way. You can compare the values of the inputs by calling .value on your ngModel.

you can see how it works on the below block of code

 <div *ngIf="newPassword.dirty || newPassword.touched">
    <div class="alert alert-danger" *ngIf="newPassword.value.match(passwordCurrent.value)">
        Current password and New Password can't match.
    </div>
</div>

Long Version of the code

    <div class="form-group">
        <div class="input-group input-group-alternative">
            <div class="input-group-prepend">
                                                <span class="input-group-text"><i
                                                        class="fa fa-unlock-alt"></i></span>
            </div>
            <input required minlength="6" [(ngModel)]="passwordOneIn"
                   #newPassword="ngModel" name="newPassword" class="form-control"
                   placeholder="New Password" type="password">
        </div>
    </div>

    <!--#####check if the password and current password match########-->
    <div *ngIf="newPassword.dirty || newPassword.touched">
        <div class="alert alert-danger" *ngIf="newPassword.value.match(passwordCurrent.value)">
            Current password and New Password can't match.
        </div>
    </div>

    <div *ngIf="newPassword.invalid && (newPassword.dirty || newPassword.touched)"
         class="alert alert-danger">
        <div *ngIf="newPassword.errors.required">
            password is required.
        </div>
    </div>

    <div class="form-group">
        <div class="input-group input-group-alternative">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fa fa-unlock-alt"></i></span>
            </div>
            <input required minlength="6" [(ngModel)]="passwordTwoIn"
                   #retypedPassword="ngModel" name="retypedPassword"
                   class="form-control" placeholder="Retype Password" type="password">
        </div>
    </div>


    <div *ngIf="retypedPassword.invalid && (retypedPassword.dirty || retypedPassword.touched)"
         class="alert alert-danger">
        <div *ngIf="retypedPassword.errors.required">
            password is required.
        </div>
    </div>

    <!--##### check if the new password and retyped password match ########-->
    <div *ngIf="retypedPassword.dirty || retypedPassword.touched">
        <div class="alert alert-danger" *ngIf="!newPassword.value.match(retypedPassword.value)">
            New password and Retyped Password don't match.
        </div>
    </div>

    <button [disabled]="changePasswordFrom.form.invalid || newPassword.value.match(passwordCurrent.value) || !newPassword.value.match(retypedPassword.value)"
            type="submit" class="btn btn-primary mt-4">Change Password</button>
</div>

Related