Search code examples
angulartypescriptvalidationangular-forms

How to show warning message for unmatched confirm password


I have a reactive Angular form. I have two fields 'Password' and 'Confirm Password' in the form. So, normally when one puts an unmatching password in the confirm password section it is supposed to show the "password did not match" message. When the field is not filled, it will show the "mandatory field" message.

Problem: When I enter the unmatching password the warning message I put on the HTML is not showing.

app.component.ts

import { Component, ViewChild, OnInit } from '@angular/core';
import { NgFor } from '@angular/common';
import { userInfo } from './Models/UserInfo';
import { CSCService } from './Services/csc.service';
import { NgForm, AbstractControl, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'AllinOne';
  editRecordId = null;
  formData:any[] = [];
  @ViewChild('f') Forms!: NgForm;
  genders = ['male', 'female'];
  public user:any=[];
  // user = {
  //   id: "",
  //   username: "",
  //   email: "",
  //   phoneNumber: "",
    
  //   states: [],
  //   cities: [],
  //   gender: "",
  // }


  form = ['one', 'two', 'three'];
  selected = 'two'
  submitted = false;

  public check:boolean=true;

  // Password confirmation function
  checkPass(pas1:any,pas2:any){
    console.log(pas1.value);
    console.log(pas2.value);
    if (pas1.value == pas2.value)
    {
      this.check=false;
      console.log("Matched");
    }
    else
    {
      this.check=true;
      console.warn("Not matched");
    }
  }

  constructor(private formBuilder: FormBuilder, private cscService: CSCService) {
    this.user=new userInfo('','','','','','','','','','');
  }
  
  ngOnInit() {
    this.country=this.cscService.country();
    console.log(this.country);
    this.state=this.cscService.state();
    console.log(this.state);
    this.city=this.cscService.city();
    console.log(this.city);
  }
  country:any=[];
  state:any=[];
  city:any=[];
  onSelectCountry(country:any){
    console.log(country.target.value);
    this.state=this.cscService.state().filter(e=>e.did==country.target.value);
    console.log(this.state);
    
  }
  onSelectState(state:any){
    console.log(state.target.value);
    this.city=this.cscService.city().filter(f=>f.sid==state.target.value);
    console.log(this.city);
  }



  onEdit(user:any) {
    // destructure user object separate ID and rest of the fields
    // coz we didn't have ID field so to avoid error dont use ID
    const {id, ...data} = user

    // set edit record ID
    this.editRecordId = id;

    // set form value with selected user
    this.Forms.setValue(data)

  }

  onDelete(user:any) {
    // filter out deleted entry from form data array matching
    // with the ID of deleted user record with ID from form data array
    this.formData = this.formData.filter((data:any) => data.id !== user.id)
  }

  onSubmit(): void{
    this.submitted = true;

    if (this.editRecordId) {
        // check if already exist record in formData matches with the
        // edit reocrd id that means its edited record then return newly
        // submitted form value else return old formData record
        // and populate formData array.
        this.formData = this.formData.map((data:any) => data.id === this.editRecordId ? this.Forms.value : data)

        // rest edit record id to null again
        this.editRecordId = null;
      } else {
        // assigning unique ID to each record I used timestamp technically it would be database primary key ID
        const id = Date.now(); 

        const data = {
          id,
          ...this.Forms.value
        }
        this.formData.push(data)
      }
     
  
    this.Forms.reset();
  }
}

app.component.html

<div class="form-group">
      <label>Password</label>
      <input type="text" class="form-control" placeholder="Password" #pas1="ngModel" [class.is-invalid]="pas1.invalid && (pas1.dirty||pas1.touched)" pattern="((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{12,30})" required class="form-control" [(ngModel)]="user.pas1" class="form-control" name="pas1" required>
      <section *ngIf="pas1.invalid && (pas1.dirty||pas1.touched)">
          <small class="text-danger" *ngIf="pas1.hasError('required')">
              Password is <strong>required</strong>
          </small>
          <small class="text-danger" *ngIf="pas1.hasError('pattern')">
              Password must be atleast <strong>12 characters long, must have a special character, a capital letter and a small letter</strong>
          </small>
      </section>
  </div>
  <div class="form-group">
      <label>Confirm Password</label>
      <input type="text" class="form-control" placeholder="Confirm Password" #pas2="ngModel" [class.is-invalid]="pas2.invalid && checkPass(pas1,pas2)" [(ngModel)]="user.pas2" name="pas2" (keyup)="checkPass(pas1,pas2)" required>
      <section *ngIf="pas2.invalid && (pas2.dirty||pas2.touched)">
          <small class="text-danger" *ngIf="pas2.hasError('required')">
              <strong>Mandatory field</strong>
          </small>
          <small class="text-danger" *ngIf="pas2.hasError('pattern')">
              <strong>Password did not match</strong>
          </small>
          
      </section>
  </div>

Solution

  • For the pas2 control, you are showing the error when the form control is invalid:

    *ngIf="pas2.invalid && (pas2.dirty||pas2.touched)"
    

    As you are doing custom validation, you need to set the error to pas2 control when the validation is failed.

    this.Forms.controls['pas2'].setErrors({ noMatch: true });
    

    And don't forget to remove the error if the validation is passed.

    Complete code:

    checkPass(pas1: any, pas2: any) {
      console.log(pas1.value);
      console.log(pas2.value);
      if (pas1.value == pas2.value) {
        this.check = false;
        console.log('Matched');
    
        this.Forms.controls['pas2'].setErrors({ noMatch: null });
      } else {
        this.check = true;
        console.warn('Not matched');
    
        this.Forms.controls['pas2'].setErrors({ noMatch: true });
      }
    }
    

    As mentioned by @Eliseo, rename "pattern" to "noMatch" for better error definition and avoid using the same error name if you apply the "pattern" validation in the pas2 control.

    <small class="text-danger" *ngIf="pas2.hasError('noMatch')">
      <strong>Password did not match</strong>
    </small>
    

    Demo @ StackBlitz