Skip to content

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 #33705

Description

@CSchulz

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:

  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

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 (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-<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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions