Signal Forms: [formField] bound to a CVA control (e.g. mat-chip-listbox) crashes with "this.field(...) is not a function" on a native double-click
- Dominant language
- TypeScript
- Stars
- 25k
- Forks
- 6.8k
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 91
Description
### Which @angular/* package(s) are the source of the bug?
forms
### Is this a regression?
No
### Description
Binding `[formField]` to a component that Signal Forms integrates via the `ControlValueAccessor` bridge (e.g. `mat-chip-listbox`) crashes with `TypeError: this.field(...) is not a function` when **two click events fire synchronously in the same JS turn** - which is exactly what a native double-click produces (browsers dispatch two `click` events before `dblclick`).
This is not specific to a dynamic/record-keyed field - it reproduces with a single, plain, statically-named field on a minimal one-field form. It also surfaces the related error `NG01902: Orphan field, looking for property '' of ` on the same trigger.
Reproduced two ways:
- `el.click(); el.click();` on the chip option, in the same script turn (no animation/timing involved)
- Playwright's `.dblclick()`
A `debounce()` on the field does not prevent it (tested both applied and unset).
### Minimal reproduction
```ts
import { Component, signal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { form, FormField } from '@angular/forms/signals';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { MatChipsModule } from '@angular/material/chips';
interface Model {
color: string;
}
@Component({
selector: 'app-root',
standalone: true,
imports: [FormField, MatChipsModule],
template: `
Red
Blue
`,
})
export class App {
model = signal({ color: '' });
colorForm = form(this.model);
}
bootstrapApplication(App, { providers: [provideAnimationsAsync()] });
```
Steps:
1. Load the page.
2. Double-click either chip option (or, via devtools console:
`document.querySelector('mat-chip-option').click(); document.querySelector('mat-chip-option').click();`).
Verified locally against exactly this minimal component (isolated from any larger app code) with both trigger methods above - 0 errors on a single click, crashes reliably on every double-click / synchronous double-`click()`.
### Please provide a link to a minimal reproduction of the bug
https://github.com/CSchulz/angular-signal-forms-dblclick-repro
### Please provide the exception or error you saw
```true
ERROR TypeError: this.field(...) is not a function
at FormField.state.ngDevMode.debugName [as computation] (@angular_forms_signals.js:3109:38)
at Object.producerRecomputeValue (chunk-....js:300:25)
at producerUpdateValueVersion (chunk-....js:110:8)
at _FormField.computed2 [as state] (chunk-....js:263:5)
at _FormField.ɵngControlUpdate (@angular_forms_signals.js:2910:31)
at Object.update (chunk-....js:10664:33)
at controlUpdateInternal (chunk-....js:8546:14)
at Module.ɵɵcontrol (chunk-....js:8532:3)
and, on repeated triggering:
RuntimeError: NG01902: Orphan field, looking for property 'color' of .
```
### Please provide the environment you discovered this bug in (run `ng version`)
```true
Angular CLI: 22.0.6
Angular: 22.0.6
@angular/material: 22.0.4
@angular/cdk: 22.0.4
Node.js: 24.15.0
TypeScript: 6.0.3
rxjs: 7.8.2
```
### Anything else?
Re-verified 2026-08-19 against:
- **22.2.0-next.2/22.2.0-next.3** (`next` dist-tag, i.e. the newest prerelease, not yet promoted to `latest`) - same crash, not fixed there either
- Also reproduces with `provideNoopAnimations()` instead of `provideAnimationsAsync()` - not animation-timing-related
### Is `mat-chip-listbox` required, or would any CVA control do?
Every frame in the stack trace above is `@angular/forms/signals` code - no `@angular/material` code appears in it, so the defect itself is squarely in Signal Forms. That said, a hand-written, Material-free `ControlValueAccessor` component (tried both a plain two-`` version and a more faithful one with nested child components, `ChangeDetectorRef.markForCheck()`, and an
`EventEmitter`) does **not** reproduce the crash under the same conditions (`page.dblclick()` on a never-before-clicked element). So `mat-chip-listbox` isn't strictly required, but something about its specific interaction pattern is a necessary trigger for this repro, and that trigger has not been identified yet - this app has no `zone.js` dependency (it's zoneless), which already rules out one candidate (CDK's `FocusMonitor`/`MatRipple` moving work in and out of `NgZone`, since without zone.js those calls are inert no-ops).
### Additional context
- We first hit this in an app with a *dynamic* per-key record model bound via `[formField]="form[dynamicKey]"` to a `mat-chip-listbox` inside an `@for` loop - but the minimal repro above shows the dynamic-key/loop aspect isn't required at all; a single static field on a single-field form is enough.
- Workaround: bind the CVA-based control manually (`[value]`/`(change)` reading and writing `field().value` directly) instead of `[formField]`, bypassing the affected path entirely - at the cost of losing automatic `touched`/`disabled`/focus wiring for that control.
Contributor guide
Research direction
Start with the linked minimal reproduction and reproduce the synchronous double-click failure, then inspect the @angular/forms/signals stack around FormField.state and ɵngControlUpdate, comparing one click with two same-turn clicks through the ControlValueAccessor bridge. Done means the reproduction no longer reports the TypeError or NG01902 and the bound mat-chip-listbox remains usable on repeated clicks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100