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;
}
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.
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('');
}
}