You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Which @angular/* package(s) are relevant/related to the feature request?
forms
Description
While the current forms module already supports reactivity using valueChanges and statusChanges and we can use RxJS / Signal interoperability helpers to work with signals in our reactive forms, there's still quite a gap when it comes to form handling with the new signal API.
While I believe that a complete rewrite of the Forms Module that is more aligned to the simplistic API design of signals, inject helper and signal-based component helper and is also purely based on signals for any form state, I also think that this requires a lot of time to rethink and build. I like the proposal of @ShacharHarshuv#51786, and it provides some good ideas about a simplistic API aligned to signals. However, there are still many things to consider (custom control values that are non-primitives and can cause confusion with groups (TValue first vs TControl first discussion in Typed forms discussion).
In the meantime, it would be great to have some updates to the existing form modules that would allow us to work with signal-based state when handling forms. Also, it would be great to expose any form-based state as reactive values contrary to the sometimes limited valueChanges and statusChanges. This would allow the combination of form state using computed and the execution of side-effects using effect to fulfil many form handling use cases.
Proposed solution
The proposed solution is to include signals for all relevant form states within AbstractControl and update them whenever the local state of the form controls gets updated. This way, developers can benefit from the new reactive form state in both reactive and template-driven forms since NgForm / NgModel uses AbstractControl under the hood. This is a feasible and non-intrusive alternative before a possible rewrite of the Form Module.
Within this proposal, the signals of AbstractControl that represent the form controls state are grouped within a new signals object inside AbstractControl. This allows the regular form state and signals to coexist without causing name clashes or overcomplicated lengthy prefixes. This also encourages the extraction of specific signals using destructuring const {value, valid} = myControl.signals;.
For template-driven forms using NgForm and NgModel, the current idea is to provide a way to inject your signals from your component into the controls using ngFormOptions and ngModelOptions. Since the developer does not have control over the form control creation in template-driven forms, that's the simplest way to connect forms created within the template to some signals within your component. An alternative idea would be to obtain the NgModel directive within the component using ViewChild and then receive the signals from the exposed control. However, that would require you to wait for AfterViewInit or even AfterViewChecked, and this would compromise the declarative approach, which is possible by passing manually created signals into the controls.
To provide the necessary convenience to use external form control signals, I've added a helper function, createFormSignals. In the current version of the PR, I'm also experimenting with making the value signal a WritableSignal. This way, in the most simple way to use this proposal in conjunction with NgModel / NgForm, the value signal can be used to update the form value programmatically from within the component. Currently, this is done via an effect inside abstract control, and a lot of testing needs to be done to make this robust enough and ensure a consistent state while not causing any loops.
This simple example with reactive forms shows that even when FormControl does not natively use signals, we still benefit from the newly exposed signals object and can create larger reactive constructs quite declaratively.
With template-driven forms, we can achieve similar reactive handling by using the ngFormOptions and ngModelOptions directive inputs. Since NgModel or NgForm model binding is optional, the existing template-driven forms can become the default tool when creating signal-based forms with this proposal.
@Component({selector: 'app-simple-template',standalone: true,imports: [FormsModule],template: ` <input type="text" reqired ngModel [ngModelOptions]="{signals: searchQuery}" /> <div>Results: {{results()}}</div> `})exportclassSimpleTemplateComponent{searchService=inject(SearchService);searchQuery=createFormSignals('');results=signal([]asstring[]);searchEffect=effect(async()=>{if(this.searchQuery.value().length>3){this.results.set(awaitlastValueFrom(this.searchService.search(this.searchQuery.value())));// Use the writable value signal to update the form programmatically (currently experimenting with this)this.searchQuery.value.set('');}},{allowSignalWrites: true});}
I've created a draft PR with the changes required for this new feature. The draft PR does not contain any tests, no documentation and probably has a few bugs. #53481
I think it's a great way to ship the benefits of signals to Angular forms quickly. I also agree that there might be a place to completely rethink forms in Angular, and simplify the DX around them. (Like in my proposition)
I have just updated the proposal to include a more streamlined way to create form signals for use in NgForm and NgModel. I've also experimentally included that value is now a WritableSignal on AbstractControlSignals. The examples are updated, and so are the NPM library and Stackblitz.
Which @angular/* package(s) are relevant/related to the feature request?
forms
Description
While the current forms module already supports reactivity using
valueChanges
andstatusChanges
and we can use RxJS / Signal interoperability helpers to work with signals in our reactive forms, there's still quite a gap when it comes to form handling with the new signal API.While I believe that a complete rewrite of the Forms Module that is more aligned to the simplistic API design of signals, inject helper and signal-based component helper and is also purely based on signals for any form state, I also think that this requires a lot of time to rethink and build. I like the proposal of @ShacharHarshuv #51786, and it provides some good ideas about a simplistic API aligned to signals. However, there are still many things to consider (custom control values that are non-primitives and can cause confusion with groups (TValue first vs TControl first discussion in Typed forms discussion).
In the meantime, it would be great to have some updates to the existing form modules that would allow us to work with signal-based state when handling forms. Also, it would be great to expose any form-based state as reactive values contrary to the sometimes limited
valueChanges
andstatusChanges
. This would allow the combination of form state usingcomputed
and the execution of side-effects usingeffect
to fulfil many form handling use cases.Proposed solution
The proposed solution is to include signals for all relevant form states within
AbstractControl
and update them whenever the local state of the form controls gets updated. This way, developers can benefit from the new reactive form state in both reactive and template-driven forms since NgForm / NgModel usesAbstractControl
under the hood. This is a feasible and non-intrusive alternative before a possible rewrite of the Form Module.Within this proposal, the signals of
AbstractControl
that represent the form controls state are grouped within a newsignals
object insideAbstractControl
. This allows the regular form state and signals to coexist without causing name clashes or overcomplicated lengthy prefixes. This also encourages the extraction of specific signals using destructuringconst {value, valid} = myControl.signals;
.For template-driven forms using
NgForm
andNgModel
, the current idea is to provide a way to inject your signals from your component into the controls usingngFormOptions
andngModelOptions
. Since the developer does not have control over the form control creation in template-driven forms, that's the simplest way to connect forms created within the template to some signals within your component. An alternative idea would be to obtain theNgModel
directive within the component usingViewChild
and then receive the signals from the exposed control. However, that would require you to wait forAfterViewInit
or evenAfterViewChecked
, and this would compromise the declarative approach, which is possible by passing manually created signals into the controls.To provide the necessary convenience to use external form control signals, I've added a helper function,
createFormSignals
. In the current version of the PR, I'm also experimenting with making thevalue
signal a WritableSignal. This way, in the most simple way to use this proposal in conjunction with NgModel / NgForm, the value signal can be used to update the form value programmatically from within the component. Currently, this is done via an effect inside abstract control, and a lot of testing needs to be done to make this robust enough and ensure a consistent state while not causing any loops.This simple example with reactive forms shows that even when
FormControl
does not natively use signals, we still benefit from the newly exposed signals object and can create larger reactive constructs quite declaratively.With template-driven forms, we can achieve similar reactive handling by using the
ngFormOptions
andngModelOptions
directive inputs. Since NgModel or NgForm model binding is optional, the existing template-driven forms can become the default tool when creating signal-based forms with this proposal.Or a little more complex template-driven form:
I've created a draft PR with the changes required for this new feature. The draft PR does not contain any tests, no documentation and probably has a few bugs. #53481
I've also published the forms module containing those changes to a temporary NPM package and included it in a stackblitz for you to play around: https://stackblitz.com/edit/stackblitz-starters-uyjg2x?file=src%2Freactive-form.component.ts,src%2Ftemplate-driven-form-signals-option.component.ts,src%2Ftemplate-driven-form.component.ts,src%2Fsignal-based-forms.component.ts
I would love to hear your feedback, thoughts and ideas!
Cheers
Gion
Alternatives considered
n/a
The text was updated successfully, but these errors were encountered: