Forms

Custom form field components that work with all Angular form strategies: Signal Forms, Reactive Forms, and Template-driven Forms. Each component implements FormValueControl<T> or FormCheckboxControl for Signal Forms, and optional ControlValueAccessor adapters (NgOatValueCva, NgOatCheckboxCva) enable formControlName, [formControl], and [(ngModel)] binding.

Validation errors display only after the field is touched (blurred or interacted with).

ComponentSelectorSignal FormsReactive / ngModel
Input<ng-oat-input>FormValueControl<string>NgOatValueCva
Textarea<ng-oat-textarea>FormValueControl<string>NgOatValueCva
Select<ng-oat-select>FormValueControl<string>NgOatValueCva
Checkbox<ng-oat-checkbox>FormCheckboxControlNgOatCheckboxCva
Switch<ng-oat-switch>FormCheckboxControlNgOatCheckboxCva
Radio Group<ng-oat-radio-group>FormValueControl<string>NgOatValueCva
Form Error<ng-oat-form-error>[control][errors] + [show]

Import & Usage

typescript
import { Component, signal } from '@angular/core';
import { FormField, FormRoot, form, schema, required, email } from '@angular/forms/signals';
import { NgOatInput, NgOatSelect, NgOatCheckbox, NgOatFormError } from '@letsprogram/ng-oat';

@Component({
  selector: 'app-example',
  imports: [FormField, FormRoot, NgOatInput, NgOatSelect, NgOatCheckbox, NgOatFormError],
  template: `
    <form [formRoot]="myForm" (ngSubmit)="onSubmit()">
      <ng-oat-input label="Email" type="email" [formField]="myForm.email" />
      <ng-oat-form-error [control]="myForm.email" />
      <ng-oat-checkbox label="Terms" [formField]="myForm.terms" />
      <button type="submit">Submit</button>
    </form>
  `,
})
export class ExampleComponent {
  private model = signal({ email: '', terms: false });
  myForm = form(this.model, schema($ => {
    required($.email);
    email($.email);
  }));
  onSubmit() { console.log(this.model()); }
}

Complete Form

Plan
html
// Component class
model = signal({ name: '', email: '', bio: '', country: '', plan: 'free', terms: false, newsletter: false });

profileForm = form(this.model, schema($ => {
  required($.name);
  minLength($.name, 2);
  required($.email);
  email($.email);
  required($.country);
}));

// Template
<form [formRoot]="profileForm" (ngSubmit)="onSubmit()">
  <ng-oat-input label="Name" [formField]="profileForm.name" />
  <ng-oat-form-error [control]="profileForm.name" />

  <ng-oat-input label="Email" type="email" [formField]="profileForm.email" />
  <ng-oat-form-error [control]="profileForm.email" />

  <ng-oat-textarea label="Bio" [formField]="profileForm.bio" />
  <ng-oat-select label="Country" [options]="countries" [formField]="profileForm.country" />
  <ng-oat-form-error [control]="profileForm.country" />

  <ng-oat-radio-group label="Plan" [options]="plans" [formField]="profileForm.plan" />
  <ng-oat-checkbox label="Accept terms" [formField]="profileForm.terms" />
  <ng-oat-switch label="Newsletter" [formField]="profileForm.newsletter" />
  <button type="submit">Submit</button>
</form>

Standalone (no form)

Each component also works standalone with two-way binding via [(value)] or [(checked)].

Input:

Checkbox: false

Switch: true

html
<ng-oat-input label="Name" [(value)]="name" />
<ng-oat-checkbox label="Remember me" [(checked)]="remember" />
<ng-oat-switch label="Dark mode" [(checked)]="darkMode" />