Skip to content

Signal forms: support non-string types for radio inputs and type-check radio values #65726

@NielsMommen

Description

@NielsMommen

Which @angular/* package(s) are relevant/related to the feature request?

forms

Description

Radio input requires FieldTree<string>

With the new signal forms API, it is currently only possible to bind FieldTree<string> to <input type="radio" />. This effectively means all radio options in the form model must be strings.

For example:

@Component({
  selector: 'app-radio-group',
  imports: [Field],
  template: `
    <fieldset>
      <legend>Do you like the new signal forms API?</legend>
      <div>
        <input id="option-yes" type="radio" [field]="questionForm.likeSignalForms" value="true" />
        <label for="option-yes">Yes</label>
      </div>
      <div>
        <input id="option-no" type="radio" [field]="questionForm.likeSignalForms" value="false" />
        <label for="option-no">No</label>
      </div>
    </fieldset>
  `,
  styles: ``,
})
export class RadioGroup {
  private readonly questionModel = signal<{ likeSignalForms: 'true' | 'false' }>({
    likeSignalForms: 'true',
  });
  protected readonly questionForm = form(this.questionModel);
}

This forces the model to use string literal types.

Lack of type-checking for value on radio inputs

Another related issue: the value binding for radio inputs is not type-checked against the form model. It is currently possible to bind any value, even if it is not part of the model’s union type, and the compiler does not complain.

import { Component, effect, signal } from '@angular/core';
import { Field, form } from '@angular/forms/signals';

@Component({
  selector: 'app-radio-group',
  imports: [Field],
  template: `
    <fieldset>
      <legend>Do you like the new signal forms API?</legend>
      <div>
        <input id="option-yes" type="radio" [field]="questionForm.likeSignalForms" [value]="true" />
        <label for="option-yes">Yes</label>
      </div>
      <div>
        <input
          id="option-no"
          type="radio"
          [field]="questionForm.likeSignalForms"
          [value]="'doesNotExistInModel'"
        />
        <label for="option-no">No</label>
      </div>
    </fieldset>
  `,
  styles: ``,
})
export class RadioGroup {
  private readonly questionModel = signal<{ likeSignalForms: 'true' | 'false' }>({
    likeSignalForms: 'true',
  });
  protected readonly questionForm = form(this.questionModel);

  constructor() {
    effect(() => {
      console.log(this.questionModel().likeSignalForms);
    });
  }
}

Selecting the second radio button sets the model value to doesNotExistInModel, which is not a valid value according to the model type.

Ideally, the template should be type-checked so that the [value] binding for a given radio group must be assignable to the corresponding field type in the model.

Proposed solution

Allow non-string field types for radio inputs

Allow binding a FieldTree<T> where T is not necessarily string (e.g. boolean):

@Component({
  selector: 'app-radio-group',
  imports: [Field],
  template: `
    <fieldset>
      <legend>Do you like the new signal forms API?</legend>
      <div>
        <input id="option-yes" type="radio" [field]="questionForm.likeSignalForms" [value]="true" />
        <label for="option-yes">Yes</label>
      </div>
      <div>
        <input id="option-no" type="radio" [field]="questionForm.likeSignalForms" [value]="false" />
        <label for="option-no">No</label>
      </div>
    </fieldset>
  `,
  styles: ``,
})
export class RadioGroup {
  private readonly questionModel = signal<{ likeSignalForms: boolean }>({ likeSignalForms: true });
  protected readonly questionForm = form(this.questionModel);
}

Currently this fails with:
TS2322: Type 'boolean' is not assignable to type 'string'

Type-check [value] against the field type

Make the type of [value] for a given <input type="radio" [field]="..." /> be constrained to the field’s type. For example, if the model says:

signal<{ likeSignalForms: 'true' | 'false' }>(...)

then [value] for that radio group should only accept 'true' | 'false', and using 'doesNotExistInModel' should be a compilation error.

This would prevent invalid model states and make signal forms more type-safe and ergonomic.

Alternatives considered

  • Use string values and map on submit
    Keep the field as 'true' | 'false', then map to boolean (or another type) when handling form submission.
    Downside: more boilerplate, and the form model doesn’t reflect the real domain types.
  • Wrap in a custom radio group control
    Implement a custom component that internally handles mapping between primitive HTML value strings and richer types.
    Downside: duplicates logic that might be better handled by the forms/signals integration itself.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions