Angular Reactive Form with Bootstrap - Custom validation is not working in animation

731 Views Asked by At

I created a Reactive Form in Angular 16, and added Bootstrap validation to it, normal built-in validators work fine, adding a custom validator also adds the error to the errors array but Bootstrap still shows the input element as valid.

bootstrap-form.component.html:

<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 cannot be less than 8 characters
                    </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>

bootstrap-form.component.ts:

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;
}

error-screenshot error-animation-screenshot

As you can see in the attached screenshots, the errors array has a nerror but Bootstrap still shows it in green.

I just tried out this code, and I can't understand what's wrong here so I don't know what to try out.

1

There are 1 best solutions below

0
On BEST ANSWER

According to Form validation in Bootstrap docs,

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

Although the Reactive form throws an error for the password field, it doesn't set the error in the constraint validation API.


Approach 1: Use minLength attribute

From the validatePassword function, you are validating the password minimum length, you can 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 replace the validatePassword with Validators.minLength(8) for the form control validation

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

Approach 2: Update the error message to Validation API

If you are keen to use the Angular Reactive Form built-in/custom validation without the HTML attribute for the constraint validation API, you need to update the error message in the constraint validation API for each <input> element via 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('');
  }
}

Demo @ StackBlitz