NEURALNG
FOUNDATION

Forms Integration

Use one typed component value across Angular Signal Forms, Reactive Forms, template-driven Forms and direct Signal model bindings.

Signal Forms nativeTyped valuesShared conformance tests

CHOOSE THE OWNER

One control, four binding styles

Choose one source of truth for each form. NeuralNg adapts to the chosen Angular form model; it does not create a second validation store or synchronize competing form systems.

Signal Forms

Recommended for new Signal-first applications

Typed Signal model, schema validation and direct field state.

Reactive Forms

Excellent for established and complex applications

Explicit controls, synchronous state and Observable integration.

Template-driven

Best for small and simple forms

Minimal setup through name and two-way ngModel binding.

Direct models

Useful outside a form boundary

Signal model inputs such as value, checked and their generated change outputs.

Angular 22 marks Signal Forms stable. It is the recommended path for new NeuralNg applications built around Signals. Reactive Forms remains the conservative choice for existing production systems and Observable-heavy workflows.

The adapters share the same component state

These controls are bound through three different Angular form systems. Programmatic writes, disabled state and touch propagation use the same public component contract.

Model: [email protected]Model: [email protected]
Model: Istanbul

Native hosts and custom controls stay distinct

Native inputs should not receive a redundant ControlValueAccessor. Composite controls implement Angular's Signal Forms control interfaces directly and expose the same model to every adapter.

ContractControlsBehavior
Native hosts Input, Textarea Native input/textarea value accessors and submission semantics remain intact.
Value controls AutoComplete, DatePicker, FileUpload, InputMask, InputNumber, InputOtp, MultiSelect, Password, Radio, Select, Slider, TreeSelect, TriStateCheckbox Implement FormValueControl<T> with one typed value model.
Checkbox controls Checkbox, Switch Implement FormCheckboxControl with one boolean checked model.

Do not add ngDefaultControl and do not wrap a NeuralNg control in an application-owned CVA. Bind the form directive directly to the NeuralNg host.

Signal Forms

Create a writable Signal model, derive a typed field tree with form(), and bind each leaf with [formField]. Schema rules own validation, disabled, readonly, hidden and async state.

account-form.ts TypeScript
import { Component, signal } from '@angular/core';
import { FormField, email, form, required } from '@angular/forms/signals';
import { NeuralInput } from '@neural-ng/core/input';

@Component({ imports: [FormField, NeuralInput] })
export class AccountForm {
  readonly model = signal({ email: '' });
  readonly accountForm = form(this.model, (path) => {
    required(path.email, { message: 'Email is required.' });
    email(path.email, { message: 'Enter a valid email address.' });
  });
}

<input neuralInput type="email" [formField]="accountForm.email" />

Reactive Forms

Bind FormControl, formControlName and typed groups directly. Programmatic setValue, reset, disabled state and touch status flow into the component without a parallel value store.

shipping-form.ts TypeScript
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { NeuralSelect } from '@neural-ng/core/select';

@Component({ imports: [ReactiveFormsModule, NeuralSelect] })
export class ShippingForm {
  readonly city = new FormControl<string | null>(null, Validators.required);
}

<neural-select
  [options]="cities"
  [formControl]="city"
  ariaLabel="Shipping city"
/>

Template-driven Forms

Use [(ngModel)] for compact forms. Supply name whenever the control participates in a parent form; use ngModelOptions.standalone only when it intentionally does not.

newsletter-form.ts TypeScript
import { FormsModule } from '@angular/forms';
import { NeuralInput } from '@neural-ng/core/input';

@Component({ imports: [FormsModule, NeuralInput] })
export class NewsletterForm {
  email = '';
}

<form #newsletter="ngForm" (ngSubmit)="subscribe()">
  <input
    neuralInput
    type="email"
    name="email"
    [(ngModel)]="email"
    required
  />
</form>

Validation belongs to the form; presentation belongs to Field

NeuralField does not execute validators. It connects labels, hints and errors, and reflects invalid, required, pending, disabled and readonly state into consistent visual and ARIA relationships.

validated-field.html HTML
<neural-field
  controlId="work-email"
  required
  [invalid]="accountForm.email().touched() && accountForm.email().invalid()"
  [pending]="accountForm.email().pending()"
>
  <label neuralFieldLabel>Work email</label>
  <input neuralInput type="email" [formField]="accountForm.email" />
  <small neuralFieldHint>Used for account notifications.</small>

  @if (accountForm.email().touched() && accountForm.email().invalid()) {
    <small neuralFieldError>
      {{ accountForm.email().errors()[0]?.message }}
    </small>
  }
</neural-field>
For Signal Forms, treat field signals such as invalid(), touched(), errors() and pending() as the source of truth. Mirrored native attributes improve behavior and accessibility but native :invalid is not the Signal Forms validation API.

Model events and semantic events answer different questions

The model output reports value synchronization. Semantic events such as selectionChange, stateChange or selected describe an actual user interaction and include component-specific context.

events.html HTML
<neural-select
  [(value)]="city"
  (valueChange)="modelChanged($event)"
  (selectionChange)="userSelected($event)"
/>

<!-- Programmatic writes update value/valueChange through the owning binding.
     selectionChange remains reserved for pointer or keyboard selection. -->

Programmatic write

Updates the rendered value and form state. It must not masquerade as pointer or keyboard interaction.

User interaction

Updates the model and emits the control's semantic event once with source and previous value.

Preserve state semantics and nullability

Touched

Emitted when focus leaves the control, not on every value write.

Disabled

Blocks interaction and receives state from the owning form adapter.

Readonly

Remains focusable for inspection while blocking user mutation.

Required

Constraint and ARIA state remain separate from the value type.

Pending

Async validation or application work is visible without inventing a value.

Nullable values

Select and DatePicker can return null; do not erase null with unsafe casts.

Use FormControl<string | null> when the component can clear to null. Use nonNullable only for controls whose public value contract cannot produce null.

Submission is owned by the form boundary

Use a real form and a submit Button so Enter, browser automation and accessibility tools retain native behavior. Validate through the selected Angular form API before persisting data. Reset through that same owner rather than manually clearing each child.

account.html HTML
<form (submit)="save($event)" novalidate>
  <neural-field controlId="email">...</neural-field>

  <neural-button type="submit" label="Create account" />
  <neural-button type="reset" label="Reset" severity="secondary" />
</form>

SSR and conformance testing

Initial form values, disabled rules and validation visibility must match on server and browser. NeuralNg's shared conformance suites exercise direct, Signal, Reactive and template adapters for programmatic writes, user events, readonly, disabled, required, touch, focus and reset.

city-control.spec.ts TypeScript
it('writes through the form without a semantic user event', async () => {
  cityControl.setValue('Ankara');
  fixture.detectChanges();
  await fixture.whenStable();

  expect(select.value()).toBe('Ankara');
  expect(selectionEvents()).toHaveLength(0);
});

it('marks the control touched when focus leaves', () => {
  trigger.focus();
  trigger.blur();
  expect(cityControl.touched).toBe(true);
});