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
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 48/100
- Issue-Typ
- Bug
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- angular, typescript
- Bereich
- frontend
Rechercherichtung
Beginne mit der verlinkten Minimalreproduktion und reproduziere den Fehler beim synchronen Doppelklick. Untersuche anschließend den @angular/forms/signals-Stack rund um FormField.state und ɵngControlUpdate und vergleiche einen Klick mit zwei Klicks im selben Turn über die ControlValueAccessor-Bridge. Als erledigt gilt die Aufgabe, wenn die Reproduktion nicht mehr TypeError oder NG01902 meldet und die gebundene mat-chip-listbox bei wiederholten Klicks weiterhin verwendbar bleibt.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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 '<key>' of <root> 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
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: `
<mat-chip-listbox [formField]="colorForm.color">
<mat-chip-option value="red">Red</mat-chip-option>
<mat-chip-option value="blue">Blue</mat-chip-option>
</mat-chip-listbox>
`,
})
export class App {
model = signal<Model>({ color: '' });
colorForm = form(this.model);
}
bootstrapApplication(App, { providers: [provideAnimationsAsync()] });
Steps:
- Load the page.
- 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
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 <root>.
Please provide the environment you discovered this bug in (run ng version)
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 (
nextdist-tag, i.e. the newest prerelease, not yet promoted tolatest) - same crash, not fixed there either - Also reproduces with
provideNoopAnimations()instead ofprovideAnimationsAsync()- 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-<button> 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 amat-chip-listboxinside an@forloop - 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 writingfield().valuedirectly) instead of[formField], bypassing the affected path entirely - at the cost of losing automatictouched/disabled/focus wiring for that control.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 25k
- Forks
- 6.8k
- Ø Merge
- 1 T. 1 Std.
- Gemergte PRs (30 T.)
- 84
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus angular/components
-
area: material/tree docs gemini-triaged needs triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
angular/components#33832 ·
-
area: material/datepicker gemini-triaged P4
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
angular/components#33763 · 2 Kommentare · 3 Reaktionen ·
-
area: material/table gemini-triaged P4
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
angular/components#33709 · 1 Kommentar ·
-
area: material/table docs gemini-triaged P4
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
angular/components#33455 ·
-
area: material/core gemini-triaged P3
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 78/100
angular/components#33059 · 2 Kommentare ·
Alle Issues in angular/components
Ähnliche Issues
-
bug(cli): hapi doctor inline-media prints a fabricated B:\ helper-script path in packaged installs Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
-
Crush Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
catppuccin/catppuccin#3125 ·
-
Add a SECURITY.md Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
ElementsProject/cln-application#167 · 1 Kommentar · 1 Reaktion ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Quantco/pnpm-licenses#17 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100