The integration of Angular Reactive Form with Bootstrap is causing issues with custom validation not functioning properly during animation

After creating a Reactive Form in Angular 16 and adding Bootstrap validation to it, I encountered an issue where normal built-in validators work fine but custom validators do not reflect properly. Even though the error is added to the errors array, Bootstrap still shows the input element as valid.

In my bootstrap-form.component.html file, the code looks like this:

<div class="container">
    <div class="row">
        <div class="col">
            <h2 class="text-center fs-3 semibold">
                {{ loginForm.value | json }}
            </h2>
            <form class="needs-validation" [formGroup]="loginForm" novalidate>
                <div class="mt-4">
                    <label for="username-input" class="form-label fs-4">
                        username
                    </label>
                    <input
                        type="text"
                        id="username-input"
                        placeholder="username"
                        class="form-control mt-2"
                        formControlName="username"
                        required
                    />
                    <div
                        class="invalid-feedback"
                        *ngIf="
                            loginForm.controls['username'].hasError('required')
                        "
                    >
                        Username cannot be empty
                    </div>
                </div>
                <div class="mt-4">
                    <label for="password-input" class="form-label fs-4">
                        password
                    </label>
                    <input
                        type="password"
                        id="password-input"
                        placeholder="password"
                        class="form-control mt-2"
                        formControlName="password"
                        required
                    />
                    <div
                        class="invalid-feedback"
                        *ngIf="
                            loginForm.controls['password'].hasError('required')
                        "
                    >
                        Password cannot be empty
                    </div>
                    <div
                        class="invalid-feedback"
                        *ngIf="
                            loginForm.controls['password'].errors?.['passwordInvalid']
                        "
                    >
                        Password must be at least 8 characters long
                    </div>
                    <h3 class="fs-6">
                        {{ loginForm.controls["password"].errors | json }}
                    </h3>
                </div>
                <div class="mt-4">
                    <button type="submit" class="btn btn-primary col-12">
                        Login
                    </button>
                </div>
            </form>
        </div>
    </div>
</div>

In the bootstrap-form.component.ts file, here is the relevant code snippet:

import { Component, OnInit } from '@angular/core';
import {
    AbstractControl,
    FormBuilder,
    FormControl,
    FormGroup,
    Validators,
} from '@angular/forms';

@Component({
    selector: 'app-bootstrap-form',
    templateUrl: './bootstrap-form.component.html',
    styleUrls: ['./bootstrap-form.component.css'],
})
export class BootstrapFormComponent implements OnInit {
    loginForm: FormGroup;

    constructor(private formBuilderService: FormBuilder) {
        this.loginForm = this.formBuilderService.group({
            username: ['', [Validators.required]],
            password: ['', [Validators.required, validatePassword]],
            phoneNumber: ['', [Validators.required]],
        });
    }

    ngOnInit(): void {
        let form = document.querySelector('form') as HTMLFormElement;
        form.addEventListener('submit', (submitEvent: SubmitEvent) => {
            if (!form.checkValidity()) {
                submitEvent.preventDefault();
                submitEvent.stopPropagation();
            }

            form.classList.add('was-validated');
        });
    }
}

export function validatePassword(
    formControl: AbstractControl
): { [key: string]: any } | null {
    if (formControl.value && formControl.value.length < 8) {
        return { passwordInvalid: true };
    }
    return null;
}

https://i.sstatic.net/n8BGS.png https://i.sstatic.net/nb84d.png

Upon reviewing the attached screenshots, it is evident that the errors array contains an error, yet Bootstrap displays the field as valid. This discrepancy has left me puzzled, and I am unsure of what steps to take next in troubleshooting this issue.

If anyone can offer insight into what might be causing this unexpected behavior, I would greatly appreciate it.

Answer №1

As stated in the Bootstrap documentation on form validation,

All modern browsers support the constraint validation API, which consists of JavaScript methods for validating form controls.

Even though an error is thrown by the Reactive form for the password field, it does not set the error in the constraint validation API.


Method 1: Utilize the minLength attribute

In the validatePassword function, when you are checking the minimum length of the password, simply add the minLength="8" attribute to the <input> element.

<input
    type="password"
    id="password-input"
    placeholder="password"
    class="form-control mt-2"
    formControlName="password"
    required
    minlength="8"
/>

Note that you can substitute validatePassword with Validators.minLength(8) for form control validation.

password: ['', [Validators.required, Validators.minLength(8)]]

Method 2: Modify the error message for Validation API

If you prefer using Angular Reactive Form's built-in/custom validation without utilizing the HTML attribute for the constraint validation API, you must update the error message in the constraint validation API for each <input> element using setCustomValidity(error).

<input
    #passwordInput
    type="password"
    id="password-input"
    placeholder="password"
    class="form-control mt-2"
    formControlName="password"
    required
    (input)="validatePasswordInput(passwordInput)"
/>
validatePasswordInput(passwordField: HTMLInputElement) {
  if (this.loginForm.controls['password'].errors) {
    for (let error in this.loginForm.controls['password'].errors)
      passwordField.setCustomValidity(
        this.loginForm.controls['password'].errors[error]
      );
  } else {
    // No error
    passwordField.setCustomValidity('');
  }
}

View Demo on StackBlitz

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

Enumeration of checkbox elements

I am completely new to React and I would like some help with building a list of checkboxes from an enumeration in my project. I have searched for examples but none of them exactly match what I need. So, let's say I have this enum: declare enum option ...

PIXI.js fails to optimize image loading and loads the same image multiple times when a base URL is used

I'm in the process of developing a game using PIXI.js that will be accessed through URL X but loaded on another website at URL Y. To make this possible, I've implemented an environment variable called BASE_URL. This variable is set to '&apo ...

The prototype's function doesn't pause for anything, carrying out its duty in a continuous cycle

I have been attempting to enhance the prototype of an object by adding an asynchronous function to be called later on. Here is my approach: const ContractObject = Object; ContractObject.prototype['getBalance'] = async function(userId: number ...

Leverage async and await features in TypeScript aiming at ES5 compatibility

Currently working on a TypeScript project that is set to target ES5, I am exploring the feasibility of incorporating async/await functionality. Syntactically, the TypeScript compiler is able to transpile the code without issues. However, it has come to my ...

When performing an arithmetic operation, the right operand must be a data type of 'any', 'number', 'bigint', or an enumeration type

My JavaScript code needs to be converted to TypeScript for proper functionality. categoryAxis.renderer.labels.template.adapter.add("dy", function(dy, target) { if (target.dataItem && target.dataItem.index % 2 === 0) { return dy + 25; } ...

Design a custom Bootstrap dropdown using an input[type="text"] element

After exploring the Bootstrap dropdown example here, I realized that for my particular scenario, it would be more beneficial to have an input field (type="text") instead of a button. This way, I can display the selected option from the dropdown. Is there ...

Maximizing Jest's potential with multiple presets in a single configuration file/setup

Currently, the project I am working on has Jest configured and testing is functioning correctly. Here is a glimpse of the existing jest.config.js file; const ignores = [...]; const coverageIgnores = [...]; module.exports = { roots: ['<rootDir&g ...

html The "download" attribute causing a new tab to open rather than initiating a

I have a website built with Angular 4, where users can download images from an Amazon S3 bucket. However, I encountered an issue wherein instead of downloading the image, it opens in a new tab. I've tested this problem on different browsers such as Fi ...

Jest Matcher issue: the value received must either be a promise or a function that returns a promise

As a dedicated practitioner of TDD, I am currently working on implementing an Exception in my code. Below is the test code I have written: it.each([[{ id: '', token: '', skills: [''] }, 'Unknown resource']])( ...

What is the correct method for decreasing the width of tab labels in Angular Material?

Addressing the Issue Given that /deep/, >>>, and ::ng-deep are no longer recommended, what is the appropriate approach to reduce the width of mat-tab-label which has a minimum width of 160px on desktop devices? Is there a way to achieve this wit ...

Is it necessary for nested Bootstrap 3 rows to be enclosed within a col-sm-*?

Is it necessary for nested Bootstrap 3 rows to be within a col-sm-* or can they also be nested within a col-md-, col-xs-, etc? The documentation specifies col-sm-* but does not clearly state if nesting within other col sizes is not allowed. http://getboo ...

Accessing the attribute of an element from a custom directive in Angular 2+ is an essential read

I am currently in the process of creating a custom directive that involves reading an attribute (formControlName) from the native element and then potentially adding additional attributes based on certain conditions. However, I encountered an issue when a ...

Attempting to deploy an Angular 9 web project within Visual Studio

Starting my first Angular project has been a smooth process so far. After successfully running "npm start" without any errors, I encountered some issues when launching the application from Visual Studio. Despite sending the same commands, I consistently ...

7 Tips for Renaming Variables in VSCode without Using the Alias `oldName as newName` StrategyWould you like to

When working in VSCode, there is a feature that allows you to modify variables called editor.action.rename, typically activated by pressing F2. However, when dealing with Typescript and Javascript, renaming an imported variable creates aliases. For exampl ...

Trigger a modal to open based on a specific condition

I have successfully implemented a default modal from Materialize, but now I am looking to add a conditional opening feature to it. In my application, there is a countdown timer and I want the modal to automatically appear when the countdown reaches a certa ...

Connecting to a nearby version of Bootstrap causes CSS to malfunction

Currently, I am facing an issue while developing an angular+bootstrap application for my work. At the initial stages of development, I encountered a problem where all the CSS breaks whenever I link to a local copy of Bootstrap, whether minified or not. My ...

"Time" for creating a date with just the year or the month and year

I am trying to convert a date string from the format "YYYYMMDD" to a different format using moment.js. Here is the code snippet I am using: import moment from 'moment'; getDateStr(date: string, format){ return moment(date, 'YYYYMMDD&a ...

A standard set of code used to manage various HTTP methods such as retrieving data, updating data, adding new

Previously in angularjs or angular-1, there was a shared code snippet to manage various http methods like GET, PUT, POST, DELETE, // Used for GET, PUT, POST, DELETE requests sharedService.process(method, url, data, headers).then(function(result){ / ...

Adding dependency service to the parent class in Angular

I am working with classes parent and child. The child class is an extension of the parent class. I want to inject the injectable class service into the parent class since all instances of the child class will be using it as well. Can someone guide me on ...

Conditional Column Rendering in BootstrapVue's b-table

Is there a way to display a specific column only if the user accessing the page is an admin? I am using bootstrapVue but unsure of how to achieve this. Any suggestions or recommendations on how to proceed? ...