From 40f7fbdccbfadd92e02623f5e5f3933d6772a68b Mon Sep 17 00:00:00 2001 From: whit33y Date: Fri, 12 Jun 2026 18:47:04 +0200 Subject: [PATCH] docs: clarify value attribute on radio/checkbox inputs is allowed with formField --- .../references/signal-forms.md | 64 +++++++++++-------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/skills/dev-skills/angular-developer/references/signal-forms.md b/skills/dev-skills/angular-developer/references/signal-forms.md index d40c9bcdee38..b702e6b6874d 100644 --- a/skills/dev-skills/angular-developer/references/signal-forms.md +++ b/skills/dev-skills/angular-developer/references/signal-forms.md @@ -170,10 +170,20 @@ Do _NOT_ bind the `name` field. When using `[formField]`, you MUST NOT set the following attributes in the template (either static or bound): - `min`, `max` (Use validators in the schema instead) -- `value`, `[value]`, `[attr.value]` (Already handled by `[formField]`) +- `value`, `[value]`, `[attr.value]` on **text/number/date inputs** (Already handled by `[formField]`) - `[attr.min]`, `[attr.max]` - `[disabled]`, `[readonly]` (Already handled by `[formField]`) +**Exception**: Static `value` on `` and `` is **allowed and required** — it identifies which option the input represents, not the bound field value. + +```html + + + + + +``` + Do NOT do this: `` or ``. ```html @@ -506,32 +516,32 @@ form( ## Common Pitfalls (DO NOT DO THESE) -| Error Scenario | WRONG (Common Mistake) | RIGHT (Correct Way) | -| :--------------------- | :-------------------------------------------- | :---------------------------------------------------------- | -| **Accessing Flags** | `form.field.valid()` | `form.field().valid()` | -| **Accessing value** | `form.field.value()` | `form.field().value()` | -| **Setting value** | `form.field.set(x)` | Update model signal: `this.model.update(...)` | -| **Form root flags** | `form.invalid()` | `form().invalid()` | -| **Double-calling** | `form.field()()` | `form.field().value()` | -| **Rules Context** | `({ touched }) => touched()` | `({ state }) => state.touched()` | -| **Calling Paths** | `applyWhen(p.foo, () => p.foo() === 'x')` | `applyWhen(p.foo, ({ valueOf }) => valueOf(p.foo) === 'x')` | -| **applyWhen args** | `applyWhen(condition, () => {...})` | `applyWhen(path, condition, schemaFn)` - needs 3 args | -| **Array length** | `form.items().length` | `form.items.length` (structural) | -| **Multi-select array** | `` | Use `readonly()` rule in schema | -| **min/max attributes** | `` | Use `min()` and `max()` rules in schema | -| **value binding** | `` | Do NOT use `[value]` with `[formField]` | -| **when option** | `pattern(p.x, /.../, {when: ...})` | `when` only works with `required()` | -| **Submit callback** | `submit(form, () => { ... })` | `submit(form, async () => { ... })` | -| **Async params** | `params: s.field` | `params: ({ value }) => value()` | -| **Async onError** | Omitting `onError` | `onError` is REQUIRED in `validateAsync` | -| **resource() API** | `request: signal` | `params: signal` | -| **applyEach args** | `applyEach(s.items, (item, index) => ...)` | `applyEach(s.items, (item) => ...)` | -| **Nested @for** | `$parent.$index` | Use `let outerIndex = $index` | -| **FormState import** | `import { FormState }` | `FormState` does not exist, use `FieldState` | -| **Null in model** | `signal({ name: null })` | `signal({ name: '' })` or `signal({ age: 0 })` | -| **Validate syntax** | `validate(s.field, { value } => ...)` | `validate(s.field, ({ value }) => ...)` | -| **Checkbox Array** | `[formField]="form.tags"` (string[]) | Checkboxes ONLY bind to `boolean` | +| Error Scenario | WRONG (Common Mistake) | RIGHT (Correct Way) | +| :--------------------- | :-------------------------------------------- | :------------------------------------------------------------------------------- | +| **Accessing Flags** | `form.field.valid()` | `form.field().valid()` | +| **Accessing value** | `form.field.value()` | `form.field().value()` | +| **Setting value** | `form.field.set(x)` | Update model signal: `this.model.update(...)` | +| **Form root flags** | `form.invalid()` | `form().invalid()` | +| **Double-calling** | `form.field()()` | `form.field().value()` | +| **Rules Context** | `({ touched }) => touched()` | `({ state }) => state.touched()` | +| **Calling Paths** | `applyWhen(p.foo, () => p.foo() === 'x')` | `applyWhen(p.foo, ({ valueOf }) => valueOf(p.foo) === 'x')` | +| **applyWhen args** | `applyWhen(condition, () => {...})` | `applyWhen(path, condition, schemaFn)` - needs 3 args | +| **Array length** | `form.items().length` | `form.items.length` (structural) | +| **Multi-select array** | `` | Use `readonly()` rule in schema | +| **min/max attributes** | `` | Use `min()` and `max()` rules in schema | +| **value binding** | `` | Do NOT use `[value]` with `[formField]` (static `value` on radio/checkbox is OK) | +| **when option** | `pattern(p.x, /.../, {when: ...})` | `when` only works with `required()` | +| **Submit callback** | `submit(form, () => { ... })` | `submit(form, async () => { ... })` | +| **Async params** | `params: s.field` | `params: ({ value }) => value()` | +| **Async onError** | Omitting `onError` | `onError` is REQUIRED in `validateAsync` | +| **resource() API** | `request: signal` | `params: signal` | +| **applyEach args** | `applyEach(s.items, (item, index) => ...)` | `applyEach(s.items, (item) => ...)` | +| **Nested @for** | `$parent.$index` | Use `let outerIndex = $index` | +| **FormState import** | `import { FormState }` | `FormState` does not exist, use `FieldState` | +| **Null in model** | `signal({ name: null })` | `signal({ name: '' })` or `signal({ age: 0 })` | +| **Validate syntax** | `validate(s.field, { value } => ...)` | `validate(s.field, ({ value }) => ...)` | +| **Checkbox Array** | `[formField]="form.tags"` (string[]) | Checkboxes ONLY bind to `boolean` | ## Big Form Example