Angular Form Validation

Form Validation

Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and Angular Forms Validation.

Custom styles

For custom CoreUI form validation messages, you’ll need to add the noValidate boolean property to your form. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you’ll see the :invalid and :valid styles applied to your form controls.

Custom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback.

import { Component, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
  ButtonDirective,
  ColComponent,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormSelectDirective,
  GutterDirective,
  InputGroupComponent,
  InputGroupTextDirective,
  RowDirective
} from '@coreui/angular';

@Component({
  selector: 'docs-validation-custom-styles',
  templateUrl: './validation-custom-styles.component.html',
  imports: [
    ReactiveFormsModule,
    FormsModule,
    FormDirective,
    RowDirective,
    GutterDirective,
    ColComponent,
    FormLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    InputGroupComponent,
    InputGroupTextDirective,
    FormSelectDirective,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    ButtonDirective
  ]
})
export class ValidationCustomStylesComponent {
  readonly customStylesValidated = signal(false);

  onSubmit1() {
    this.customStylesValidated.set(true);
    console.log('Submit... 1');
  }

  onReset1() {
    this.customStylesValidated.set(false);
    console.log('Reset... 1');
  }
}
<form #customStylesForm="ngForm"
      (ngSubmit)="onSubmit1()"
      [gutter]="3"
      [validated]="customStylesValidated()"
      novalidate
      cForm cRow
      class="needs-validation"
>
  <c-col [md]="4">
    <label cLabel for="validationCustom01">First name</label>
    <input cFormControl id="validationCustom01" required type="text" value="Mark" />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationCustom02">Last name</label>
    <input cFormControl id="validationCustom02" required type="text" value="Otto" />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationCustomUsername">Username</label>
    <c-input-group class="has-validation">
      <span cInputGroupText id="inputGroupPrepend">&#64;</span>
      <input aria-describedby="inputGroupPrepend"
             cFormControl
             id="validationCustomUsername"
             required
             type="text"
      />
      <c-form-feedback [valid]="false">Please choose a username.</c-form-feedback>
    </c-input-group>
  </c-col>
  <c-col [md]="6">
    <label cLabel for="validationCustom03">City</label>
    <input cFormControl id="validationCustom03" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid city.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationCustom04">State</label>
    <select cSelect id="validationCustom04" required>
      <option value="">Choose...</option>
      <option>...</option>
    </select>
    <c-form-feedback [valid]="false">Please provide a valid State.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationCustom05">Zip code</label>
    <input cFormControl id="validationCustom05" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid zip.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <c-form-check>
      <input cFormCheckInput id="invalidCheck" name="invalidCheck" required type="checkbox" />
      <label cFormCheckLabel for="invalidCheck">Agree to terms and conditions</label>
    </c-form-check>
    <c-form-feedback [valid]="false">You must agree before submitting.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <button cButton class="me-1" color="primary" type="submit">
      Submit form
    </button>
    <button (click)="onReset1()" cButton color="secondary" type="reset">
      Reset
    </button>
  </c-col>
</form>

Browser defaults

Not interested in custom validation feedback messages or writing JavaScript to change form behaviors? All good, you can use the browser defaults with ngNativeValidate. Try submitting the form below. Depending on your browser and OS, you’ll see a slightly different style of feedback.

While these feedback styles cannot be styled with CSS, you can still customize the feedback text through JavaScript.

import { Component, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
  ButtonDirective,
  ColComponent,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormSelectDirective,
  GutterDirective,
  InputGroupComponent,
  InputGroupTextDirective,
  RowDirective
} from '@coreui/angular';

@Component({
  selector: 'docs-validation-browser-defaults',
  templateUrl: './validation-browser-defaults.component.html',
  imports: [
    ReactiveFormsModule,
    FormsModule,
    FormDirective,
    RowDirective,
    GutterDirective,
    ColComponent,
    FormLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    InputGroupComponent,
    InputGroupTextDirective,
    FormSelectDirective,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    ButtonDirective
  ]
})
export class ValidationBrowserDefaultsComponent {
  readonly browserDefaultsValidated = signal(false);

  onSubmit2() {
    this.browserDefaultsValidated.set(true);
    console.log('Submit... 2');
  }

  onReset2() {
    this.browserDefaultsValidated.set(false);
    console.log('Reset... 3');
  }
}
<form #browserDefaultsForm="ngForm"
      (ngSubmit)="onSubmit2()"
      [gutter]="3"
      [validated]="browserDefaultsValidated()"
      cForm
      cRow
      ngNativeValidate
>
  <c-col [md]="4">
    <label cLabel for="validationDefault01">Email</label>
    <input cFormControl id="validationDefault01" required type="text" value="Mark" />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationDefault02">Email</label>
    <input cFormControl id="validationDefault02" required type="text" value="Otto" />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationDefaultUsername">Username</label>
    <c-input-group class="has-validation">
      <span cInputGroupText id="inputGroupPrepend1">&#64;</span>
      <input aria-describedby="inputGroupPrepend1"
             cFormControl
             id="validationDefaultUsername"
             required
             type="text"
      />
      <c-form-feedback [valid]="false">Please choose a username.</c-form-feedback>
    </c-input-group>
  </c-col>
  <c-col [md]="6">
    <label cLabel for="validationDefault03">City</label>
    <input cFormControl id="validationDefault03" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid city.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationDefault04">State</label>
    <select cSelect id="validationDefault04" required>
      <option value="">Choose...</option>
      <option>...</option>
    </select>
    <c-form-feedback [valid]="false">Please provide a valid State.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationDefault05">Zip code</label>
    <input cFormControl id="validationDefault05" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid zip.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <c-form-check>
      <input cFormCheckInput id="invalidCheck1" name="invalidCheck" required type="checkbox" />
      <label cFormCheckLabel for="invalidCheck1">Agree to terms and conditions</label>
    </c-form-check>
    <c-form-feedback [valid]="false">You must agree before submitting.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <button cButton class="me-1" color="primary" type="submit">
      Submit form
    </button>
    <button (click)="onReset2()" cButton color="secondary" type="reset">
      Reset
    </button>
  </c-col>
</form>

Server side

We recommend using client-side validation, but in case you require server-side validation, you can indicate invalid and valid form fields with valid boolean property.

For invalid fields, ensure that the invalid feedback/error message is associated with the relevant form field using aria-describedby (noting that this attribute allows more than one id to be referenced, in case the field already points to additional form text).

Input group needs an extra .has-validation class to fix border radius issues with c-form-feedback element.

import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
  ButtonDirective,
  ColComponent,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormSelectDirective,
  GutterDirective,
  InputGroupComponent,
  InputGroupTextDirective,
  RowDirective
} from '@coreui/angular';

@Component({
  selector: 'docs-validation-server-side',
  templateUrl: './validation-server-side.component.html',
  imports: [
    ReactiveFormsModule,
    FormsModule,
    FormDirective,
    RowDirective,
    GutterDirective,
    ColComponent,
    FormLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    InputGroupComponent,
    InputGroupTextDirective,
    FormSelectDirective,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    ButtonDirective
  ]
})
export class ValidationServerSideComponent {}
<form [gutter]="3" cForm cRow class="needs-validation" ngNativeValidate>
  <c-col [md]="4">
    <label cLabel for="validationServer01">First name</label>
    <input [valid]="true"
           cFormControl
           id="validationServer01"
           required
           type="text"
           value="Mark"
    />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationServer02">Last name</label>
    <input [valid]="true"
           cFormControl
           id="validationServer02"
           required
           type="text"
           value="Otto"
    />
    <c-form-feedback [valid]="true">Looks good!</c-form-feedback>
  </c-col>
  <c-col [md]="4">
    <label cLabel for="validationServerUsername">Username</label>
    <c-input-group class="has-validation">
      <span cInputGroupText id="inputGroupPrepend03">&#64;</span>
      <input [valid]="false"
             aria-describedby="inputGroupPrepend03"
             cFormControl
             id="validationServerUsername"
             required
             type="text"
      />
      <c-form-feedback [valid]="false">Please choose a username.</c-form-feedback>
    </c-input-group>
  </c-col>
  <c-col [md]="6">
    <label cLabel for="validationServer03">City</label>
    <input [valid]="false" cFormControl id="validationServer03" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid city.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationServer04">State</label>
    <select [valid]="false" cSelect id="validationServer04">
      <option disabled>Choose...</option>
      <option>...</option>
    </select>
    <c-form-feedback [valid]="false">Please provide a valid state.</c-form-feedback>
  </c-col>
  <c-col [md]="3">
    <label cLabel for="validationServer05">Zip code</label>
    <input [valid]="false" cFormControl id="validationServer05" required type="text" />
    <c-form-feedback [valid]="false">Please provide a valid zip.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <c-form-check class="mb-3">
      <input [valid]="false" cFormCheckInput id="invalidCheckServer" required type="checkbox">
      <label cFormCheckLabel for="invalidCheckServer">Agree to terms and conditions</label>
    </c-form-check>
    <c-form-feedback [valid]="false">You must agree before submitting.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <button cButton color="primary" type="submit">
      Submit form
    </button>
  </c-col>
</form>

Supported elements

Validation styles are available for the following form controls and components:

  • input cFormControl
  • select cSelect
  • c-form-check
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
  ButtonDirective,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormSelectDirective
} from '@coreui/angular';

@Component({
  selector: 'docs-validation-supported-elements',
  templateUrl: './validation-supported-elements.component.html',
  imports: [
    ReactiveFormsModule,
    FormsModule,
    FormDirective,
    FormLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    FormSelectDirective,
    ButtonDirective
  ]
})
export class ValidationSupportedElementsComponent {}
<form [validated]="true" cForm>
  <div class="mb-3">
    <label cLabel class="form-label" for="validationTextarea">
      Textarea
    </label>
    <textarea [valid]="false"
              cFormControl
              id="validationTextarea"
              placeholder="Required example textarea"
              required
    ></textarea>
    <c-form-feedback [valid]="false">Please enter a message in the textarea.</c-form-feedback>
  </div>
  <c-form-check class="mb-3">
    <input cFormCheckInput id="validationFormCheck1" name="validationFormCheck1" required type="checkbox" />
    <label cFormCheckLabel for="validationFormCheck1">Check this checkbox</label>
  </c-form-check>
  <c-form-feedback [valid]="false">Example invalid feedback text</c-form-feedback>

  <c-form-check>
    <input cFormCheckInput id="validationFormCheck2" name="radio-stacked" required type="radio" />
    <label cFormCheckLabel for="validationFormCheck2">Check this radio</label>
  </c-form-check>

  <c-form-check class="mb-3">
    <input cFormCheckInput id="validationFormCheck3" name="radio-stacked" required type="radio" />
    <label cFormCheckLabel for="validationFormCheck3">Check this radio</label>
  </c-form-check>

  <c-form-feedback [valid]="false">More example invalid feedback text</c-form-feedback>

  <div class="mb-3">
    <select aria-label="select example" cSelect required>
      <option value="">Open this select menu</option>
      <option value="1">One</option>
      <option value="2">Two</option>
      <option value="3">Three</option>
    </select>
    <c-form-feedback [valid]="false">Example invalid select feedback</c-form-feedback>
  </div>

  <div class="mb-3">
    <input aria-label="file example"
           cFormControl
           id="validationText1"
           required
           type="file"
    />
    <c-form-feedback [valid]="false">Example invalid form file feedback</c-form-feedback>
  </div>

  <div class="mb-3">
    <button cButton color="primary" disabled type="submit">
      Submit form
    </button>
  </div>
</form>

Tooltips

If your form layout allows it, you can swap the text for the tooltip to display validation feedback in a styled tooltip. Be sure to have a parent with position: relative on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup.

import { Component, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
  ButtonDirective,
  ColComponent,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormSelectDirective,
  InputGroupComponent,
  InputGroupTextDirective
} from '@coreui/angular';

@Component({
  selector: 'docs-validation-tooltips',
  templateUrl: './validation-tooltips.component.html',
  imports: [
    ReactiveFormsModule,
    FormsModule,
    FormDirective,
    ColComponent,
    FormLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    InputGroupComponent,
    InputGroupTextDirective,
    FormSelectDirective,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    ButtonDirective
  ]
})
export class ValidationTooltipsComponent {
  readonly tooltipValidated = signal(false);

  onSubmit3() {
    this.tooltipValidated.set(true);
    console.log('Submit... 3');
  }

  onReset3() {
    this.tooltipValidated.set(false);
    console.log('Reset... 3');
  }
}
<form #tooltipForm="ngForm"
      (ngSubmit)="onSubmit3()"
      [validated]="tooltipValidated()"
      cForm
      class="row g-3 needs-validation"
>
  <c-col class="position-relative" [md]="4">
    <label cLabel for="validationTooltip01">First name</label>
    <input cFormControl id="validationTooltip01" required type="text" value="Mark" />
    <c-form-feedback [valid]="true" tooltip>Looks good!</c-form-feedback>
  </c-col>
  <c-col class="position-relative" [md]="4">
    <label cLabel for="validationTooltip02">Last name</label>
    <input cFormControl id="validationTooltip02" required type="text" value="Otto" />
    <c-form-feedback [valid]="true" tooltip>Looks good!</c-form-feedback>
  </c-col>
  <c-col class="position-relative" [md]="4">
    <label cLabel for="validationTooltipUsername">Username</label>
    <c-input-group class="has-validation">
      <span cInputGroupText id="inputGroupPrependTooltip">&#64;</span>
      <input aria-describedby="inputGroupPrependTooltip"
             cFormControl
             id="validationTooltipUsername"
             required
             type="text"
      />
      <c-form-feedback [valid]="false" tooltip>Please choose a username.</c-form-feedback>
    </c-input-group>
  </c-col>
  <c-col class="position-relative" [md]="6">
    <label cLabel for="validationTooltip03">City</label>
    <input cFormControl id="validationTooltip03" required type="text" />
    <c-form-feedback [valid]="false" tooltip>Please provide a valid city.</c-form-feedback>
  </c-col>
  <c-col class="position-relative" [md]="3">
    <label cLabel for="validationTooltip04">State</label>
    <select cSelect id="validationTooltip04" required>
      <option value="">Choose...</option>
      <option value="1">...</option>
    </select>
    <c-form-feedback [valid]="false" tooltip>Please provide a valid State.</c-form-feedback>
  </c-col>
  <c-col class="position-relative" [md]="3">
    <label cLabel for="validationTooltip05">Zip code</label>
    <input cFormControl id="validationTooltip05" required type="text" />
    <c-form-feedback [valid]="false" tooltip>Please provide a valid zip.</c-form-feedback>
  </c-col>
  <c-col class="position-relative" [xs]="12">
    <c-form-check>
      <input cFormCheckInput id="invalidCheckTooltip" name="invalidCheckTooltip" required type="checkbox" />
      <label cFormCheckLabel for="invalidCheckTooltip">Agree to terms and conditions</label>
    </c-form-check>
    <c-form-feedback [valid]="false" tooltip>You must agree before submitting.</c-form-feedback>
  </c-col>
  <c-col [xs]="12">
    <button cButton class="me-1" color="primary" type="submit">
      Submit form
    </button>
    <button (click)="onReset3()" cButton color="secondary" type="reset">
      Reset
    </button>
  </c-col>
</form>

Enhanced example

With Angular validators.

import { JsonPipe, NgClass } from '@angular/common';
import { Component, inject, signal } from '@angular/core';

import {
  AbstractControl,
  FormBuilder,
  FormGroup,
  ReactiveFormsModule,
  ValidationErrors,
  ValidatorFn,
  Validators
} from '@angular/forms';
import {
  ButtonDirective,
  ButtonGroupComponent,
  CardBodyComponent,
  CardComponent,
  ColComponent,
  ColDirective,
  DatePickerComponent,
  FormCheckComponent,
  FormCheckInputDirective,
  FormCheckLabelDirective,
  FormControlDirective,
  FormDirective,
  FormFeedbackComponent,
  FormLabelDirective,
  FormPasswordDirective,
  MultiSelectComponent,
  MultiSelectOptionComponent,
  RowComponent
} from '@coreui/angular';

import { ValidationFormsService } from './validation-forms.service';

/** passwords must match - custom validator */

export const passwordMismatchValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const password = control.get('password');
  const confirm = control.get('confirmPassword');
  return password && confirm && password.value !== confirm.value ? { passwordMismatch: true } : null;
};

@Component({
  selector: 'docs-validation-enhanced',
  templateUrl: './validation-enhanced.component.html',
  styleUrls: ['./validation-enhanced.component.scss'],
  providers: [ValidationFormsService],
  imports: [
    ButtonDirective,
    ButtonGroupComponent,
    CardBodyComponent,
    CardComponent,
    ColComponent,
    ColDirective,
    DatePickerComponent,
    FormDirective,
    FormCheckComponent,
    FormCheckInputDirective,
    FormCheckLabelDirective,
    FormControlDirective,
    FormFeedbackComponent,
    FormLabelDirective,
    FormPasswordDirective,
    JsonPipe,
    MultiSelectComponent,
    MultiSelectOptionComponent,
    NgClass,
    ReactiveFormsModule,
    RowComponent
  ]
})
export class ValidationEnhancedComponent {
  readonly formBuilder = inject(FormBuilder);
  readonly validationFormsService = inject(ValidationFormsService);

  readonly submitted = signal(false);
  simpleForm!: FormGroup;
  formErrors = this.validationFormsService.errorMessages;
  formControls!: string[];

  constructor() {
    this.createForm();
  }

  createForm() {
    this.simpleForm = this.formBuilder.group(
      {
        firstName: ['', [Validators.required]],
        lastName: ['', [Validators.required]],
        username: [
          '',
          [
            Validators.required,
            Validators.minLength(this.validationFormsService.formRules.usernameMin),
            Validators.pattern(this.validationFormsService.formRules.nonEmpty)
          ]
        ],
        email: ['', [Validators.required, Validators.email]],
        password: [
          '',
          [
            Validators.required,
            Validators.minLength(this.validationFormsService.formRules.passwordMin),
            Validators.pattern(this.validationFormsService.formRules.passwordPattern)
          ]
        ],
        confirmPassword: [
          '',
          [
            Validators.required,
            Validators.minLength(this.validationFormsService.formRules.passwordMin),
            Validators.pattern(this.validationFormsService.formRules.passwordPattern)
          ]
        ],
        birthday: [null as Date | null, [Validators.required]],
        framework: ['', [Validators.required]],
        accept: [false, [Validators.requiredTrue]]
      },
      { validators: passwordMismatchValidator }
    );
    this.formControls = Object.keys(this.simpleForm.controls);
  }

  onReset() {
    this.submitted.set(false);
    this.simpleForm.reset();
  }

  onValidate() {
    this.submitted.set(true);

    // stop here if form is invalid
    return this.simpleForm.status === 'VALID';
  }

  onSubmit() {
    console.warn(this.onValidate(), this.simpleForm.value);

    if (this.onValidate()) {
      // TODO: Submit form value
      console.warn(this.simpleForm.value);
      alert('SUCCESS!');
    }
  }

  isValid(ctrl: AbstractControl): boolean | undefined {
    return ctrl.touched && ctrl.valid ? true : (this.submitted() || ctrl.touched) && ctrl.invalid ? false : undefined;
  }
}
<c-row>
  <c-col [md]="6">
    <form (ngSubmit)="onSubmit()" [formGroup]="simpleForm" cForm novalidate>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="firstName">First name</label>
        @if (simpleForm.controls['firstName']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="off"
              cFormControl
              formControlName="firstName"
              id="firstName"
              placeholder="First name"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>First Name is required</div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="lastName">Last name</label>
        @if (simpleForm.controls['lastName']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="off"
              cFormControl
              formControlName="lastName"
              id="lastName"
              placeholder="Last name"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Last Name is required</div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="username">Username</label>
        @if (simpleForm.controls['username']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="off"
              cFormControl
              formControlName="username"
              id="username"
              placeholder="Username"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Username is required</div>
                } @else if (ctrl.hasError('minlength')) {
                  <div>
                    {{ formErrors['username'].minLength }}
                  </div>
                } @else if (ctrl.hasError('pattern')) {
                  <div>
                    {{ formErrors['username'].pattern }}
                  </div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="email">Email</label>
        @if (simpleForm.controls['email']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="off"
              cFormControl
              formControlName="email"
              id="email"
              placeholder="Email"
              required
              type="email"
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Email is required</div>
                } @else if (ctrl.hasError('email')) {
                  <div>
                    {{ formErrors['email']?.email }}
                  </div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="password">Password</label>
        @if (simpleForm.controls['password']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="new-password"
              cFormPassword
              formControlName="password"
              id="password"
              placeholder="Password"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Password is required</div>
                } @else if (ctrl.hasError('minlength')) {
                  <div>{{ formErrors['password'].minLength }}</div>
                } @else if (ctrl.hasError('pattern')) {
                  <div>{{ formErrors['password'].pattern }}</div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="confirmPassword">Confirm password</label>
        @if (simpleForm.controls['confirmPassword']; as ctrl) {
          <c-col [sm]="8">
            <input
              [valid]="isValid(ctrl)"
              autocomplete="off"
              cFormPassword
              formControlName="confirmPassword"
              id="confirmPassword"
              placeholder="Confirm password"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Confirmation is required</div>
                } @else if (simpleForm.hasError('passwordMismatch')) {
                  <div>
                    {{ formErrors['confirmPassword'].passwordMismatch }}
                  </div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="birthday">Date of Birth</label>
        @if (simpleForm.controls['birthday']; as ctrl) {
          <c-col [sm]="8">
            <c-date-picker
              [valid]="(!ctrl.untouched && ctrl.valid) ? true : (submitted() || !ctrl.untouched) && ctrl.invalid ? false : undefined"
              formControlName="birthday"
              id="birthday"
              required
            />
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Birthday date required</div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        <label [sm]="4" cCol cLabel="col" for="framework">Framework</label>
        @if (simpleForm.controls['framework']; as ctrl) {
          <c-col [sm]="8">
            <c-multi-select
              [valid]="(ctrl.touched && ctrl.valid) ? true : (submitted() || ctrl.touched) && ctrl.invalid ? false : undefined"
              formControlName="framework"
              id="framework"
              required
            >
              <c-multi-select-option>Angular</c-multi-select-option>
              <c-multi-select-option>Bootstrap</c-multi-select-option>
              <c-multi-select-option>React.js</c-multi-select-option>
              <c-multi-select-option>Vue.js</c-multi-select-option>
            </c-multi-select>
            @if (submitted() || ctrl.invalid) {
              <c-form-feedback [valid]="!(submitted() || ctrl.invalid)">
                @if (ctrl.hasError('required')) {
                  <div>Framework is required</div>
                }
              </c-form-feedback>
            }
          </c-col>
        }
      </c-row>
      <c-row class="mb-1">
        @if (simpleForm.controls['accept']; as ctrl) {
          <c-col>
            <c-form-check>
              <input
                [valid]="isValid(ctrl)"
                cFormCheckInput
                formControlName="accept"
                id="accept"
                required
                type="checkbox"
              />
              <label cFormCheckLabel for="accept">I accept the terms of use</label>
              <c-form-feedback [valid]="false">
                @if (ctrl.hasError('required')) {
                  <div>You have to accept our Terms and Conditions</div>
                }
              </c-form-feedback>
            </c-form-check>
          </c-col>
        }
      </c-row>
      <hr />
      <c-button-group>
        <button
          [disabled]="simpleForm.pristine || simpleForm.invalid"
          [tabindex]="'0'"
          cButton
          color="primary"
          type="submit"
        >
          Submit
        </button>
        <button
          (click)="onValidate()"
          [disabled]="simpleForm.valid"
          [tabindex]="'0'"
          cButton
          color="success"
        >
          Validate
        </button>
        <button (click)="onReset()" [tabindex]="'0'" cButton color="danger" type="reset">
          Reset
        </button>
      </c-button-group>
    </form>
  </c-col>
  <c-col [md]="6">
    <c-card [ngClass]="{ 'bg-info': simpleForm.valid, 'bg-dark': simpleForm.invalid}">
      <c-card-body class="text-white">
        <pre>Value: <code>{{ simpleForm.value | json }}</code></pre>
        <ul>
          <li>Status: {{ simpleForm.status }}</li>
          <li>Valid: {{ simpleForm.valid }}</li>
          <li>Pristine: {{ simpleForm.pristine }}</li>
          <li>Errors: {{ simpleForm.errors | json }}</li>
        </ul>
        <ul>
          @for (ctrl of formControls; track ctrl) {
            <li>
              {{ ctrl }}
              <ul>
                <li>Invalid: {{ simpleForm.controls[ctrl].invalid | json }}</li>
                <li>Pristine: {{ simpleForm.controls[ctrl].pristine | json }}</li>
                <li>Status: {{ simpleForm.controls[ctrl].status | json }}</li>
                <li>Touched: {{ simpleForm.controls[ctrl].touched | json }}</li>
                <li>Errors: {{ simpleForm.controls[ctrl].errors | json }}</li>
              </ul>
            </li>
          }
        </ul>
      </c-card-body>
    </c-card>
  </c-col>
</c-row>
.btn:disabled {
  cursor: auto;
}
input[autocomplete='off']::-webkit-contacts-auto-fill-button,
input[autocomplete='off']::-webkit-credentials-auto-fill-button {
  visibility: hidden;
  position: absolute;
  right: 0;
}
import { Injectable } from '@angular/core';

@Injectable()
export class ValidationFormsService {
  errorMessages: Record<string, any>;

  formRules = {
    nonEmpty: '^[a-zA-Z0-9]+([_ -]?[a-zA-Z0-9])*$',
    usernameMin: 5,
    passwordMin: 6,
    passwordPattern: '(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,}'
  };

  formErrors = {
    firstName: '',
    lastName: '',
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    birthday: '',
    framework: '',
    accept: false
  };

  constructor() {
    this.errorMessages = {
      firstName: {
        required: 'First name is required'
      },
      lastName: {
        required: 'Last name is required'
      },
      username: {
        required: 'Username is required',
        minLength: `Username must be ${this.formRules.usernameMin} characters or more`,
        pattern: 'Must contain letters and/or numbers, no trailing spaces'
      },
      email: {
        required: 'required',
        email: 'Invalid email address'
      },
      password: {
        required: 'Password is required',
        pattern: 'Password must contain: numbers, uppercase and lowercase letters',
        minLength: `Password must be at least ${this.formRules.passwordMin} characters`
      },
      confirmPassword: {
        required: 'Password confirmation is required',
        passwordMismatch: 'Passwords must match'
      },
      birthday: {
        required: 'Birthday date required'
      },
      framework: {
        required: 'Framework is required'
      },
      accept: {
        requiredTrue: 'You have to accept our Terms and Conditions'
      }
    };
  }
}