Which @angular/* package(s) are relevant/related to the feature request?
forms
Description
When using compatForm, there is currently no reactive (signal-based) way to access the fully unwrapped current value of the form.
Current behavior
Calling .value() on a FieldTree created via compatForm returns the raw model value. For fields backed by Reactive Forms AbstractControl instances (e.g. FormControl, FormGroup), this returns the control instance itself rather than its current value.
Minimal reproduction
import { Component, computed, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { FormField, required } from '@angular/forms/signals';
import { compatForm, extractValue } from '@angular/forms/signals/compat';
import { JsonPipe } from '@angular/common';
@Component({
selector: 'app-root',
imports: [FormField, ReactiveFormsModule, JsonPipe],
template: `
<form>
<label>Username
<input type="text" [formField]="myForm.username" />
</label>
<fieldset [formGroup]="nameForm">
<label>Firstname
<input type="text" formControlName="firstname" />
</label>
</fieldset>
</form>
<h2>Value:</h2>
<!-- ❌ Crashes: "Converting circular structure to JSON" -->
<!-- <pre>{{ myForm().value() | json }}</pre> -->
<!-- ✅ Works for plain signal fields -->
<pre>{{ myForm().value().username }}</pre>
<!-- ⚠️ Have to drill into .value manually for compat fields -->
<pre>{{ myForm().value().name.value.firstname }}</pre>
<!-- ✅ extractValue unwraps correctly, but is imperative -->
<pre>{{ valueJson() | json }}</pre>
`,
})
export class App {
protected nameForm = new FormGroup({
firstname: new FormControl('', Validators.required),
});
protected readonly myForm = compatForm(
signal({
username: '',
name: this.nameForm,
}),
(path) => {
required(path.username);
},
);
protected readonly valueJson = computed(() => {
return extractValue(this.myForm().fieldTree);
});
}
There are three problems visible here:
myForm().value() returns the FormGroup instance for compat fields. Piping the whole value through | json crashes with "Converting circular structure to JSON" because the FormGroup object contains circular references.
- To access the actual value of a compat field, you have to manually drill into the control:
myForm().value().name.value.firstname — this defeats the purpose of a unified form value.
extractValue() correctly unwraps AbstractControl values, but it is an imperative function call, not a signal.
Why wrapping extractValue in computed is not sufficient
To use extractValue reactively, you can wrap it in a computed:
protected readonly valueJson = computed(() => {
return extractValue(this.form());
});
This works partially, but has a significant limitation: extractValue reads .value() internally, which means the computed tracks the signal. But for AbstractControl-backed fields, the .value() signal returns the control instance (a stable reference that never changes), so the computed does not re-evaluate when the underlying control's value changes. The reactive chain is broken for compat fields.
Proposed solution
There should be a reactive, signal-based way to get the fully unwrapped value of a compatForm — a signal or computed that:
- Recursively resolves
AbstractControl instances to their current values (like extractValue does).
- Automatically updates when any field in the form tree changes, including fields backed by Reactive Forms controls.
Possible API ideas (non-prescriptive):
// Option A: A dedicated signal/computed on the FieldTree
const unwrapped = myForm().rawValue(); // Signal<RawValue<TModel>>
// Option B: A reactive version of extractValue
const unwrapped = extractValue.signal(myForm); // Signal<RawValue<TModel>>
// Option C: Make .value() itself unwrap AbstractControls in compat mode
const unwrapped = myForm().value(); // already unwrapped
Alternatives considered
Use case
When incrementally migrating from Reactive Forms to Signal Forms via compatForm, a common need is to reactively consume the entire form value — for example to send it to an API on submit, derive computed state from it, or display it in the template.
A reactive extractValue-equivalent would make compatForm a much smoother migration path and bring it closer to the DX of pure Signal Forms, where .value() just works.
Proposed solution
Provide a signal-based API (e.g. a rawValue signal on FieldTree, or a reactive variant of extractValue) that:
- Recursively unwraps
AbstractControl values in the tree.
- Participates in Angular's signal graph so that consumers (templates,
computed, effect) are notified when any underlying value changes — including values managed by Reactive Forms controls.
Which @angular/* package(s) are relevant/related to the feature request?
forms
Description
When using
compatForm, there is currently no reactive (signal-based) way to access the fully unwrapped current value of the form.Current behavior
Calling
.value()on aFieldTreecreated viacompatFormreturns the raw model value. For fields backed by Reactive FormsAbstractControlinstances (e.g.FormControl,FormGroup), this returns the control instance itself rather than its current value.Minimal reproduction
There are three problems visible here:
myForm().value()returns theFormGroupinstance for compat fields. Piping the whole value through| jsoncrashes with "Converting circular structure to JSON" because theFormGroupobject contains circular references.myForm().value().name.value.firstname— this defeats the purpose of a unified form value.extractValue()correctly unwrapsAbstractControlvalues, but it is an imperative function call, not a signal.Why wrapping
extractValueincomputedis not sufficientTo use
extractValuereactively, you can wrap it in acomputed:This works partially, but has a significant limitation:
extractValuereads.value()internally, which means thecomputedtracks the signal. But forAbstractControl-backed fields, the.value()signal returns the control instance (a stable reference that never changes), so thecomputeddoes not re-evaluate when the underlying control's value changes. The reactive chain is broken for compat fields.Proposed solution
There should be a reactive, signal-based way to get the fully unwrapped value of a
compatForm— a signal or computed that:AbstractControlinstances to their current values (likeextractValuedoes).Possible API ideas (non-prescriptive):
Alternatives considered
Use case
When incrementally migrating from Reactive Forms to Signal Forms via
compatForm, a common need is to reactively consume the entire form value — for example to send it to an API on submit, derive computed state from it, or display it in the template.A reactive
extractValue-equivalent would makecompatForma much smoother migration path and bring it closer to the DX of pure Signal Forms, where.value()just works.Proposed solution
Provide a signal-based API (e.g. a
rawValuesignal onFieldTree, or a reactive variant ofextractValue) that:AbstractControlvalues in the tree.computed,effect) are notified when any underlying value changes — including values managed by Reactive Forms controls.