The `action` and `onInvalid` handlers now recevie two pieces of
information:
1. The form that is being submitted
2. The specific field that the submit was triggered on
Remove the `submit()` method on field state - supporting this is complex
from a typing perspective, since the `FieldState` only knows its
`TValue` type, not the `TModel` type of its owning `FieldTree`. Rather
than try to pack additional generics on to `FieldState`, we'll just
leave the `submit` function as a standalone importable function.
Updates FormOptions to accept a submission configuration object.
This allows defining default submit options (action, validation behavior, etc.)
when creating the form, which can be overridden when calling submit().
- Add registerOnChange, _unregisterOnChange for value change callbacks
- Add registerOnDisabledChange, _unregisterOnDisabledChange for disabled callbacks
- Add disabled changes effect to notify registered callbacks
- Call onChange callbacks from updateValue with emitModelEvent flag
- Add CachingWeakMap utility for memoization
- Add wrapFieldTreeForSyncUpdates Proxy wrapper
- Intercept fieldTree().value.set() calls to sync parent immediately
- Cache wrapped trees and states to preserve identity
- Add ValueUpdateOptions type with onlySelf, emitEvent options
- Add parent notification on value changes via effect
- Add parent notification helpers: scheduleParentUpdate, notifyParentUnlessPending
- Propagate dirty/touched/pristine/untouched to parent
- Support onlySelf option to prevent parent propagation
- Add valueChanges EventEmitter that emits when source signal changes
- Add statusChanges EventEmitter that emits when status changes
- Set up effects to emit to observables
- Add reset() method with optional value parameter
- Support FormControlState unboxing ({value, disabled} format)
- Add isFormControlState helper function
This commit introduces `SignalFormControl`, a bridge implementation that allows Signal-based forms to interoperate with existing Reactive Forms infrastructure. It extends `AbstractControl` with standard methods and reactive observables while handling state propagation to parent containers.
Parse errors allow a custom control to communicate that it is currently
unable to produce a valid value.
Parse errors are reported by implementing the optional `parseErrors`
property on the `FormUiControl`. The property should be a signal of the
current parse errors.
Also renames several `*Field` types to `*FieldTree`. This aligns with the new naming of the concept after `Field` was renamed
to `FieldTree`.
Updates signal forms to pass the full `Field` directive to the class
configuration functions, rather than just the state. This allows
developers to take the element as well as the state into consideration
when deciding classes to apply.
Closes#65762
BREAKING CHANGE: The shape of `SignalFormsConfig.classes` has changed
Previously each function in the `classes` map took a `FieldState`. Now
it takes a `Field` directive.
For example if you previously had:
```
provideSignalFormsConfig({
classes: {
'my-valid': (state) => state.valid()
}
})
```
You would need to update to:
```
provideSignalFormsConfig({
classes: {
'my-valid': ({state}) => state().valid()
}
})
```
This PR makes a number of changes to the metadata API to address design
flaws in the previous API. Some of the changes include:
- Replaces the previous `MetadataKey` and `AggregateMetadataKey` with a
single unified `MetadataKey` that is used for all metadata.
- The new `MetadataKey` is only defined for fields that explicitly set
it in their schema logic
- All metadata now has reducer / aggregate behavior
- The new `MetadataKey` has an option to create a managed key which
wraps the result of its computed aggregate into some other structure
such as a `Resource` or `linkedSignal`
- There are now two APIs to create metadata keys
- `createMetadataKey` for pure computed metadata
- `createManagedMetadataKey` for metadata that manages its computation
internally
Previously, navigating a `FieldTree` in signal forms involved reactive reads
of the value of the parent field(s), both directly and via `.childrenMap()`.
This meant that on _any_ change to the value of a field, reactive
notifications would trigger updates of computeds, reruns of effects, etc.
So for example, this effect would run on every change to the form:
```ts
const f = form(signal({data: 'abc', unrelated: 0}));
effect(() => {
// accessing f.data incurs a dependency on f().value() which changes
// on every change in the whole form
console.log(f.data().value());
});
```
This is deeply counterintuitive and troublesome when attempting to write
effect logic, and also results in `computed`s unnecessarily updating.
This change introduces the concept of a "reader" computed, which memoizes
the access of a field at a given key via the reactive graph. With this, the
same `f.data` access above now depends on the `data` reader in `f` only,
which is effectively a constant computed. As a result, the effect only
reruns on changes to `data`'s value, as intended.
PR Close#65802
Adds a DI configuration option for signal forms that allows the
developer to specify CSS classes that should be automatically added
by the `Field` directive based on the field's status.
Currently we maintain the pathKeys internally, but do not expose them
through the `FieldContext`, this PR updates the `FieldContext` to expose
this property.