angular / angular/components

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

Ouverte
#33,705 1 commentaire 2 réactions 0 personnes assignées Voir sur GitHub
area: material/chips gemini-triaged
Langage dominant
TypeScript
Étoiles
25k
Forks
6.8k
Merge moyen
1 j 8 h
PR mergées (30 j)
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.

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

Commencez par la reproduction minimale liée et reproduisez l’échec du double clic synchrone, puis examinez la pile @angular/forms/signals autour de FormField.state et de ɵngControlUpdate, en comparant un clic à deux clics dans le même tour via le pont ControlValueAccessor. C’est terminé lorsque la reproduction ne signale plus TypeError ni NG01902 et que la mat-chip-listbox liée reste utilisable lors de clics répétés.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
angular, typescript
Domaine
frontend
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
48/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.