Skip to content

fix(forms): gate invalid state on touched or dirty - #17674

Open
viktorkombov wants to merge 6 commits into
masterfrom
vkombov/fix-17671
Open

viktorkombov wants to merge 6 commits into
masterfrom
vkombov/fix-17671

Conversation

@viktorkombov

Copy link
Copy Markdown
Contributor

Closes #17671

Description

With [formField], Angular writes the field's raw invalid() into the invalid input of igx-checkbox, igx-switch and igx-radio-group whenever it changes, bypassing the touched-or-dirty rule these controls apply for reactive and template-driven forms — e.g. a pristine field painted the error style right after reset() or when a cross-field rule made it required. Same ordering issue #17637 fixed for required.

  • invalid is now a gated setter on CheckboxBaseDirective and IgxRadioGroupDirective: with a bound form control, true is coerced to false while the control is untouched and pristine (checkbox/switch/radio became delegating accessors to keep @Input/@HostBinding).
  • The radio group derives invalid from the control on status changes instead of always clearing it, since Signal Forms only push validity on change.
  • Fixed the group's registerOnTouched never reaching the radio buttons (the forms directive registers before any buttons exist), so blur now marks the field touched.

Tests: 3 new regression specs (fail without the fix); full non-grid suite green (3386), lint clean. Changelog updated; no API changes.

🤖 Generated with Claude Code

Type of Change (check all that apply):

  • Bug fix
  • New functionality
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactoring (no functional changes)
  • Documentation
  • Demos
  • CI/CD
  • Tests
  • Changelog
  • Skills/Agents

Component(s) / Area(s) Affected:

How Has This Been Tested?

  • Unit tests
  • Manual testing
  • Automated e2e tests

Test Configuration:

  • Angular version:
  • Browser(s):
  • OS:

Screenshots / Recordings

Checklist:

  • All relevant tags have been applied to this PR
  • This PR includes unit tests covering all the new code (test guidelines)
  • This PR includes API docs for newly added methods/properties (api docs guidelines)
  • This PR includes feature/README.MD updates for the feature docs
  • This PR includes general feature table updates in the root README.MD
  • This PR includes CHANGELOG.MD updates for newly added functionality
  • This PR contains breaking changes
  • This PR includes ng update migrations for the breaking changes (migrations guidelines)
  • This PR includes behavioral changes and the feature specification has been updated with them
  • Accessibility (ARIA, keyboard navigation, focus management) has been verified

Copilot AI lite review requested due to automatic review settings September 28, 2026 13:16
@viktorkombov viktorkombov added switch checkbox ❌ status: awaiting-test PRs awaiting manual verification radio-group forms forms: validation Forms validation related, including ngModel.status aka VALID/INVALID/TOUCHED/PRISTINE etc. signal-forms version: 22.2.x labels Sep 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Radio-group touch forwarding can overwrite child controls’ own callbacks in two code paths.

Review effort: Lite
Findings: None

What changed in this PR

Fixes invalid-state rendering for form-bound checkbox, switch, and radio controls by gating it on touched/dirty state.

Changes:

  • Adds gated invalid accessors and radio-group validity synchronization.
  • Improves radio touch callback forwarding.
  • Adds regression tests, documentation, and changelog updates.
File Description
skills/​igniteui-angular-components/​references/​form-controls.md Documents touched/dirty invalid styling.
projects/​igniteui-angular/​switch/​src/​switch/​switch.component.ts Delegates invalid accessors to the base directive.
projects/​igniteui-angular/​switch/​src/​switch/​switch.component.spec.ts Tests cross-field invalid-state behavior.
projects/​igniteui-angular/​radio/​src/​radio/​radio.component.ts Delegates invalid accessors to the base directive.
projects/​igniteui-angular/​radio/​src/​radio/​radio-group/​radio-group.directive.ts Gates validity and forwards touch callbacks.
projects/​igniteui-angular/​radio/​src/​radio/​radio-group/​radio-group.directive.spec.ts Tests radio-group reset behavior.
projects/​igniteui-angular/​directives/​src/​directives/​checkbox/​checkbox-base.directive.ts Implements gated invalid state.
projects/​igniteui-angular/​checkbox/​src/​checkbox/​checkbox.component.ts Delegates invalid input to the base setter.
projects/​igniteui-angular/​checkbox/​src/​checkbox/​checkbox.component.spec.ts Tests reset behavior.
CHANGELOG.md Records the forms behavior fix.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

viktorkombov and others added 3 commits September 28, 2026 16:47
The group handed its onTouched callback to every radio button, which
overwrites the single callback slot a button bound to its own form
directive already filled. Marking the group's control touched from the
blur handler it already subscribes to needs no callback on the buttons
at all, so nothing can be clobbered and no per-button guard is needed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rkaraivanov
rkaraivanov previously approved these changes Oct 7, 2026
@mddragnev

mddragnev commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Regression: a touched field no longer shows invalid after it stops being readonly
Repro: https://stackblitz.com/edit/9u4qpvqp-uehht9ik?file=src%2Fapp%2Fsignal-forms-invalid-repro%2Fsignal-forms-invalid-repro.component.ts Copy the sample into the demo app on this branch to see it there.

  1. Click Touched: the controls become invalid, as expected.
  2. Click Lock: they go back to the initial state, as expected, since readonly fields aren't validated.
  3. Click Unlock: they stay valid, although the field is invalid again (field().invalid() === true). On master they correctly show invalid.

Cause:

  • Signal Forms skip validation while a field is readonly, so readonly() and invalid() change in the same tick.
  • FormField writes its inputs in a fixed order, and invalid comes before readonly. When invalid = true arrives, the setter still sees this.readonly === true and drops it.
  • After that, nothing writes invalid again.

Suggestion: keep only the touched/dirty check in the invalid setter. The disabled/readonly checks aren't needed there:

  • Signal Forms already report invalid() === false while a field is disabled or readonly.
  • updateValidityState() still checks both for reactive and template-driven forms.

Behaviour change for reactive and template-driven forms

The new check also applies to [formControl] and ngModel, not only [formField]. With a bound control, an app-set invalid = true (an [invalid] binding or code) is now ignored while the control is untouched and pristine.

I'm not sure whether this is the intended behaviour. If it is, please, mention it in the changelog for all form types;

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

checkbox forms: validation Forms validation related, including ngModel.status aka VALID/INVALID/TOUCHED/PRISTINE etc. forms radio-group signal-forms switch version: 23.0.x ❌ status: awaiting-test PRs awaiting manual verification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Checkbox, Switch, Radio Group]: Signal Forms paint fields as invalid while untouched and pristine

4 participants