owned this note
owned this note
Published
Linked with GitHub
# Design Doc - Angular Reactive Forms
author: Angular Community
created: 2020-05-08
status: [**draft** | in review | approved | implemented | obsolete]
doc url: https://hackmd.io/HHodz9FQR6mGCZxRmEimsg?both
discord discussion: https://discord.gg/gCEj4zC in #forms channel
feedback url:
design doc template version: 1.0.0
## Objective
<!--
In a few sentences, describe the key objectives.
-->
This document outlines requirements, and goals for building a new reactive forms API for Angular. A few key goals are:
- Type-safe
Angular is built on top of TypeScript to provide type-safe APIs. We want to encourage usage of type-safety when consuming these forms, and provide that type-safety throughout the API.
- Composeable
Simple use cases for forms will be covered, but we also want to cover complex use cases, such as:
* Nested validation
* Nested forms
* Complex async validation
Minimal but extensible
Relying on built-in Web APIs would be preferred where possible.
Compatible with native Angular form controls and validators (`NG_VALUE_ACCESSOR` and `NG_VALIDATORS`).
Property-renaming-safe - does not need to access controls by string (e.g. `formControlName='prop'`).
_This design doc serves to gather feedback, knowledge about existing APIs, and guidance for the proposed new APIs._
### Approvals
### Expected User/Developer Experience
<!--
Describe what will the user be able to do and how will they be able to do it thanks to this new feature. This should be done in a story format, described from the user’s/developer’s perspective, with as many details as possible but without going to into the implementation details.
-->
### Background
[Zack DeRose] I've spoken in the past about my initial experience learning RxJS, which revolved around heavy use of `BehaviorSubject`'s, and specifically avoiding operators by calling `next()` on a given `BehaviorSubject` within the subscription of some other `BehaviorSubject`.
This mixture of imperative and declarative styles lead down a dark path. Even without much complexity in the use case, using this `BehaviorSubject` approach is analygous to assembling a [Rune-Goldberg machine](https://www.youtube.com/watch?v=qybUFnY7Y8w). Making changes or fixes to such machine requires a heavy cognitive load on the developer, and simulatenously, the developer also loses the fine grain control over `Observable` streams that is normally available when using RxJS.
I see the majority of the issues with @angular/form's, is that in it's current state, a developer has no choice but to mix imperative (calling `setValue()` on a `FormControl`, implementing `writeValue()` in the `ControlValueAccessor`) with declarative when implementing reactive behavior. It's essentially the same as my above `BehaviorSubject` example, but with no alternative available.
I believe that rectifying this situation should be our paramount goal in @ngrx/forms.
<!--
Stuff one needs to know to understand this doc: motivating examples, previous versions and problems, links to related projects/design docs, etc. You should mention related work if applicable. This is background; do not write about ideas to solve problems here.
-->
### Prior Art
Current Angular Forms libraries
- [Template-Driven Forms](https://angular.io/guide/forms)
- [Reactive Forms](https://angular.io/guide/reactive-forms)
Issues:
- https://github.com/angular/angular/issues/31963
- https://github.com/angular/angular/issues/13721
Libraries:
ngneat Forms Manager - https://github.com/ngneat/forms-manager
ngx-sub-form - https://github.com/cloudnc/ngx-sub-form
ngObservableForm - https://github.com/SanderElias/ngObservableForm
rxweb/types(Strongly Typed Reactive Form) - https://github.com/rxweb/rxweb/tree/master/client-side/angular/packages/types
Formly - https://formly.dev/
Forms Typed - https://github.com/gparlakov/forms-typed/blob/master/projects/forms/README.md
## Prototype
### Detailed Design
[[ I imagine this hardly qualifies as fully-detailed ]]
The core of my concept revolves around the concept of a private Observable, for now we'll call it `_domValue$: Observable<T>`. This property is private because due to the nature of forms, our developers don't have complete control of the domValue (as by intent, control is ceded to our users).
Rather, a developer would supply a `valueOperator: Observable<T> => Observable<T>`. This operator would allow the developer to define any 'developer controled' dynamic/reactive/programmatic adjustments to the value of the form, based on the `_domValue$` of the form, as well as any other Observable in scope that should externally cause the form in question to adjust its value.
Additionally, a developer would supply a `validationOperator: Observable<T> => Observable<ValidationErrors | null>`, which would operate much like the value operator, but with Validity.
Publically, our @ngrx/forms API would then expose a `readonly value$: Observable<T>`, as well as a `readonly errors$: Observable<ValidationErrors | null>`. These would both be created by applying their respective developer-defined operators to the source `_domValue$`.
(Similar approaches could be used for `readonly touched$: Observable<void>`, `readonly enabled$: Observable<'enabled' | 'disabled'>`, `readonly submit$: Observable<T>`, and `readonly blur$: Observable<void>`)
<!--
Details on how you’ll implement. Should be in sufficient detail for other engineers to materially comment on structure to affect the end result.
-->
#### Compatability with current reactive forms API
[Jan-Niklas Wortmann] I think creating an API that is compatabile with the current reactive forms is key to the success of this module. Otherwise other third party libraries (e.g. @angular/material), but also existing CustomControls will be pain to use. On top of that additional migration effort would prevent many projects to move this direction.
To establish an API that is compatible with the current reactive forms API I see two approaches.
1. Extending the key entities by inheritance
2. Wrap the key entities with a service
##### Extending the key entities by inheritance
Technically we could extend the key entities of the reactive forms API.
```ts
export class ReactiveFormControl<T> extends FormControl {}
```
This way we could support all the existing features in a compatible way. There could be some drawbacks about the strict template type checking.
###### Benefits
- compatibility
- new features will work more or less out of the box
- fairly easy migration (could be provided with factory methods and schematics)
- easy to determine boundaries
###### Drawbacks
- tight coupling (might break in upcoming versions)
#### Wrap the key entities with a service
I think this is the approach the ngneat forms manager took. By creating a service that wraps the forms API we could extend type safety, but also generate formControls, etc. to establish a compatible way of working with existing API.
###### Benefits
- Composition over inheritance (lose coupling)
- easier to test (DI vs. classes and POJOs)
###### Drawbacks
- no easy migration
- sounds like more effort
- features added to the reactive forms API might need to be added manually to the service too
#### Registration of Controls
#### Validation/Error Handling
See `validityOperator` and `errors$` described above.
#### Testing
I envision the unit testing would look like controlling/defining the `_domValue$` (to model a user's various potential behavior), as well as mocking or defining any other streams the developer would expect to have an affect, and using marble-diagrams/the test scheduler to assert that the public observables (`value$`/`errors$`/`touched$`/`enabled$`) exposed are as expected.
### Caveats
<!--
You may need to describe what you did not do or why simpler approaches don't work. Mention other things to watch out for (if any).
Security Considerations
How you’ll be secure. Considerations should include sanitization, escaping, existing security protocols and web standards, permission, user privacy, etc.
None
-->
### Performance Considerations
<!--
How will you be fast?
Is there an impact on the payload size due to this change?
How will you benchmark the final result?
How will you prevent performance regressions due to unrelated future changes?
-->
## Documentation Plan
<!--
What types of documentation are required? [API | Concepts | Cookbooks | etc.]
Which folks will be needed to implement the docs?
CLI Integration Plan
What kind of CLI integration or changes are required for this feature to be useful to developers.
How / when will the CLI integration be done. Is a separate design doc needed?
-->
### Style Guide Impact
N/A
## Developer Impact
### Public API Surface Impact
<!--
Are you adding new public APIs? If so, what's the change? (method signature changes, new classes/interfaces, new npm packages, config file properties or new config files altogether)
Are the new APIs consistent with the existing APIs? Do they follow naming conventions and use correct terminology?
-->
### Developer Experience Impact
<!--
How will this change impact developer experience and workflows?
Are we adding new tools developers need to learn? Are we asking developers to change their workflows in any way?
Are we placing any new constraints on how the developer should write the code to be compatible with this change?
-->
### Breaking Changes
N/A
### Rollout Plan
- Integration with existing APIs?
- Angular Material integration?
### Rollback Plan
N/A
### Maintenance Plan
TBD