Building Unique Password Validation with Angular 5

I'm attempting to implement custom password validation for a password field. The password must be a minimum of 8 characters and satisfy at least two of the following criteria but not necessarily all four:

  • Contains numbers
  • Contains lowercase letters
  • Contains uppercase letters
  • Contains special characters

I have successfully implemented part of the validation, but I am encountering an issue where if the password is 8 or more characters long but does not meet at least two of the specified criteria, the error message does not appear. The error message related to character requirements only displays when the password is less than 8 characters long.

I have searched extensively on SO without finding a solution to similar issues. I suspect that the problem lies in my custom validation function not being associated with the password in ngModel.

The Question: How can I ensure that the error message is shown in the form field when the password is at least 8 characters long but does not meet the required character criteria mentioned above?

Here is the relevant code snippet.

From user-form.html:

<mat-form-field *ngIf="newPassword" fxFlex="100%">
  <input matInput #password="ngModel" placeholder="Password" type="password" autocomplete="password"
         [(ngModel)]="model.password" name="password" minlength="8" (keyup)="validatePassword(model.password)" required>
  <mat-error *ngIf="invalidPassword">
    Password must contain at least two of the following: numbers, lowercase letters, uppercase letters, or special characters.
  </mat-error>
  <mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
    <div *ngIf="password.errors.required">
      Password is required
    </div>
    <div *ngIf="password.errors.minlength">
      Password must be at least 8 characters
    </div>
  </mat-error>
</mat-form-field>

From user-form.component.ts:

export class UserFormComponent implements OnInit {


  @Input()
  user: User;

  public model: any;
  public invalidPassword: boolean;


  constructor() {}

  ngOnInit() {
    this.model = this.user;
  }


  passwordFails(checks: boolean[]): boolean {
     let counter = 0;
    for (let i = 0; i < checks.length; i++) {
      if (checks[i]) {
        counter += 1;
      }
    }
    return counter < 2;
 }


  validatePassword(password: string) {
    let hasLower = false;
    let hasUpper = false;
    let hasNum = false;
    let hasSpecial = false;

    const lowercaseRegex = new RegExp("(?=.*[a-z])");// has at least one lower case letter
    if (lowercaseRegex.test(password)) {
      hasLower = true;
    }

    const uppercaseRegex = new RegExp("(?=.*[A-Z])"); //has at least one upper case letter
    if (uppercaseRegex.test(password)) {
      hasUpper = true;
    }

    const numRegex = new RegExp("(?=.*\\d)"); // has at least one number
    if (numRegex.test(password)) {
      hasNum = true;
    }

    const specialcharRegex = new RegExp("[!@#$%^&*(),.?\":{}|<>]");
    if (specialcharRegex.test(password)) {
      hasSpecial = true;
    }

    this.invalidPassword = this.passwordFails([hasLower, hasUpper, hasNum, hasSpecial]);
  }
}

Answer №1

After some experimentation, I delved deeper into utilizing Validators within Angular. Eventually, I managed to achieve my goal with the following enhancements:

Create password-validator.directive.ts:

import { Directive, forwardRef, Attribute} from "@angular/core";
import { Validator, AbstractControl, NG_VALIDATORS} from "@angular/forms";

@Directive({
  selector: '[validatePassword]',
  providers: [
    { provide: NG_VALIDATORS, useExisting: forwardRef(() => PasswordValidator), multi: true}
  ]
})

export class PasswordValidator implements Validator {
  constructor (
    @Attribute('validatePassword')
    public invalidPassword: boolean
  ) {}

  validate(ctrl: AbstractControl): {[key: string]: any} {
    let password = ctrl.value;

    let hasLower = false;
    let hasUpper = false;
    let hasNum = false;
    let hasSpecial = false;

    const lowercaseRegex = new RegExp("(?=.*[a-z])");// has at least one lower case letter
    if (lowercaseRegex.test(password)) {
      hasLower = true;
    }

    const uppercaseRegex = new RegExp("(?=.*[A-Z])"); //has at least one upper case letter
    if (uppercaseRegex.test(password)) {
      hasUpper = true;
    }

    const numRegex = new RegExp("(?=.*\\d)"); // has at least one number
    if (numRegex.test(password)) {
      hasNum = true;
    }

    const specialcharRegex = new RegExp("[!@#$%^&*(),.?\":{}|<>]");
    if (specialcharRegex.test(password)) {
      hasSpecial = true;
    }

    let counter = 0;
    let checks = [hasLower, hasUpper, hasNum, hasSpecial];
    for (let i = 0; i < checks.length; i++) {
      if (checks[i]) {
        counter += 1;
      }
    }

    if (counter < 2) {
      return { invalidPassword: true }
    } else {
      return null;
    }



  }

}

Make the following changes in user-form.component.html:

<mat-form-field *ngIf="newPassword" fxFlex="100%">
  <input matInput #password="ngModel" placeholder="Password" type="password" autocomplete="password"
         [(ngModel)]="model.password" name="password" minlength="8" validatePassword="password" required>
  <mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
    <div *ngIf="password.errors.invalidPassword">
      Password must have two of the four: lowercase letters, uppercase letters, numbers, and special characters
    </div>
    <div *ngIf="password.errors.required">
      Password is required
    </div>
    <div *ngIf="password.errors.minlength">
      Password must be at least 8 characters
    </div>
  </mat-error>
</mat-form-field>

Revise user-form.module.ts as follows:

import {PasswordValidator} from "../password-validator.directive"; //imported to modules

@NgModule({
  imports: [
    //some modules
  ],
  declarations: [
    // some modules
    PasswordValidator // added this to declarations
  ],
  exports: [
    // stuff
  ],
  providers: [
    //stuff
  ]
})
export class UserFormModule { }

Answer №2

Check out the Stackblitz Demo

Component.html

<div class="error-text" *ngIf="myForms.get('password').hasError('passwordStrength')">
    {{myForms.get('password').errors['passwordStrength']}}
</div>

Component.ts

this.myForms = fb.group({
      password: [null, Validators.compose([
        Validators.required, Validators.minLength(8), PasswordStrengthValidator])]
});

password-strength.validators.ts

import { AbstractControl, ValidationErrors } from "@angular/forms"

export const PasswordStrengthValidator = function (control: AbstractControl): ValidationErrors | null {


let value: string = control.value || '';
  let msg="";
  if (!value) {
    return null
  }

  let upperCaseCharacters = /[A-Z]+/g;
  let lowerCaseCharacters = /[a-z]+/g;
  let numberCharacters = /[0-9]+/g;
  let specialCharacters = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/;
  if (upperCaseCharacters.test(value) === false || lowerCaseCharacters.test(value) === false || numberCharacters.test(value) === false || specialCharacters.test(value) === false) {
return {
  passwordStrength: 'Password must contain at least two of the following: numbers, lowercase letters, uppercase letters, or special characters.'
}

}

}

Answer №3

When calling the passwordFails function, ensure you are passing boolean values.

this.invalidPassword = this.passwordFails([hasLower, hasUpper, hasNum, hasSpecial]);

To modify the function so that it returns true if the password fails in any of the criteria, use the code snippet below:

passwordFails(checks: boolean[]): boolean {
   return checks.filter((x)=> typeof x === 'boolean' && x === true).length > 2
}

To add an additional condition in the DOM to check for invalid passwords:

<mat-form-field *ngIf="newPassword" fxFlex="100%">
  <input matInput #password="ngModel" placeholder="Password" type="password" autocomplete="password"
         [(ngModel)]="model.password" name="password" minlength="8" (keyup)="validatePassword(model.password)" required>
  <mat-error *ngIf="invalidPassword">
    Password must contain at least two of the following: numbers, lowercase letters, uppercase letters, or special characters.
  </mat-error>
  <mat-error *ngIf="password.invalid && (password.dirty || password.touched)">
    <div *ngIf="password.errors.required">
      Password is required
    </div>
    <div *ngIf="password.errors.minlength">
      Password must be at least 8 characters
    </div>
    <div *ngIf="!invalidPassword">
      Password must meet the following criteria.
    </div>
  </mat-error>
</mat-form-field>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Getting permission for geoLocation service on iOS in Ionic: A step-by-step guide

I have recently developed a social media application that utilizes geoLocation services. The app is built with Ionic 4 and has a Firebase backend. While the GeoLocation services are functioning properly on Android devices, users of iOS are not being prompt ...

Creating an HTML tag from Angular using TypeScript

Looking at the Angular TypeScript code below, I am trying to reference the divisions mentioned in the HTML code posted below using document.getElementById. However, the log statement results in null. Could you please advise on the correct way to reference ...

Utilizing the arr.push() method to replace an existing value within an array with a new object, rather than simply adding a new

Seeking help to dynamically render a list of components that should expand or shrink based on values being added or removed from an array of custom objects. However, facing an issue where pushing a value into the array only replaces the previous value inst ...

Oops! The Element Type provided is not valid - it should be a string

I have a desire to transition from using Victory Native to Victory Native XL. However, I am encountering an error message saying Render Error Element type is invalid. import * as React from "react"; import { View } from "react-native"; ...

Encountering parameter issues while working with Google Maps React in TypeScript

Currently, I'm utilizing TypeScript in this particular file. import React, {Component} from 'react' import {Map, InfoWindow, Marker, GoogleApiWrapper, mapEventHandler, markerEventHandler} from 'google-maps-react'; import { coordina ...

Ways to pass functions as properties to a React custom modal?

I'm currently working on a React login page setup. The login functionality is embedded in a Modal using the React-Modal library. When a user presses the login button, data is supposed to be sent to a Custom Modal as props to display a notification win ...

Exploring Ngrx: Leveraging a single selector to choose multiple property modifications

I need some help with my reactive Form Angular application that uses the NGRX store. Instead of subscribing to the entire state, I want to subscribe to changes in specific fields like "name" and "city." I have been attempting to use the selector selectFor ...

Encountered an issue trying to access undefined properties while reading 'PP'

I am trying to showcase the data retrieved from my JSON file. Here is a glimpse of the information stored in the JSON => Within DTA > PP , I am specifically interested in displaying the variable NOMFAMILLE. An error message has caught my attentio ...

Tips for accurately defining the return type for querySelector(All) connections

I enjoy doing this particular task, ensuring the types are correct. const qs = document.querySelector.bind(document) as HTMLElementTagNameMap | null; const qsa = document.querySelectorAll.bind(document) as NodeListOf<any>; While hovering over query ...

Issue with compatibility between Angular v8 application and web component polyfills in IE11 causing malfunction in rendering

Recently, I've been attempting to integrate the web component polyfills into my Angular (v8) application, but unfortunately, they don't seem to be functioning properly in IE11. To showcase this issue, I have set up a new repository containing a b ...

Defined interface with specific number of members

I am tasked with creating an interface that has only two members, one of which is optional and both have undefined names. Something like: interface MyInterface { [required: string]: string|number [optional: string]?: string|number } Unfortunately, ...

Elements can only be added to the array at the 0th index

In the process of developing a function, I encountered an issue where all elements added to the array were only stored in Array[0] of the rowData. The data is retrieved from a database. private createRowData() { var rowData:any[] = []; thi ...

Circular reference in Angular/TypeScript

I encountered a circular dependency issue in my Angular project and tried various solutions, including exporting all dependent classes from a "single file" as suggested here. Unfortunately, this approach did not work for me. I then explored other solutions ...

Angular displays incorrect HTTP error response status as 0 instead of the actual status code

In Angular, the HttpErrorResponse status is returning 0 instead of the actual status. However, the correct status is being displayed in the browser's network tab. ...

I keep receiving multiple header errors from ExpressJS even though I am positive that I am only sending a single header

Can someone please help with the issue I'm facing in the code below: router.put("/:_id", async (req: Request, res: Response) => { try { // Create the updated artist variable const artist: IArtist = req.body; const updatedArt ...

aiplafrom struggles to establish a customer using Vite alongside Vue and TypeScript

I'm currently experimenting with Gemini Pro on Vite + Vue + TS, but I encountered an issue when attempting to create an instance of PredictionServiceClient. The error message displayed is Uncaught TypeError: Class extends value undefined is not a cons ...

Implementing validation logic on button click using TypeScript

I'm struggling to get my validation to work for empty fields using the method below. Can anyone provide some guidance or suggestions? Thanks. Here is my template: <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" class="nobottommargin ad ...

The "(click)" event doesn't fire upon clicking a material icon

After creating an <a> tag with a (click) event function call, I noticed that the click event works everywhere except for the arrows mat-icons within the code snippet below: <span *ngIf="section.pages.length>0"> <mat-icon *ngIf="secti ...

Is there a way to retrieve all properties within a Typescript Class that includes optional properties?

If we have a scenario where: class ExampleType { item1?: string, item2?: string } and all the properties are OPTIONAL. Is there a way to generate an array of property names like: ["item1", "item2"] I attempted to use console.log( ...

Angular Material - truncating selected text in a list

I'm having trouble implementing the Angular Material list with checkboxes, where the text needs to be truncated instead of word-wrapped due to limited UI space. I have modified an example on the Angular Material site to demonstrate the issue. The text ...