Forms
Choose a forms approach from the repository, Angular version, and feature needs. Signal Forms can be a good fit for a repository that supports and uses them, but Reactive Forms, template-driven forms, and established wrappers are not globally wrong.
Model the Form
- Define the form value separately from the transport DTO when their shapes or semantics differ. Make defaults, optional fields, nested values, and conversions explicit.
- Identify which values are user-editable, which are derived, and which are server-controlled. Do not bind a writable form directly to shared state or mutate a store object as the user types.
- Decide what happens when initial data changes while the form is dirty: reset, merge, ignore, or ask the user. A local editing copy, including a linked signal where it fits the installed API, is useful only when that policy is intentional.
Validation
- Put validation at the narrowest level that owns the rule: field, group, or whole form. Keep cross-field and domain validation distinct from display concerns.
- Prefer reusable validation definitions when rules are shared or complex; keep a local rule local when extracting it would obscure the feature.
- Treat validation state as user behavior. Decide when errors appear, how server-side errors map to fields, how async validation is cancelled, and how messages are translated into accessible feedback. Do not rely on a boolean alone when pending, disabled, or server-invalid states matter.
Submission Lifecycle
- Model submitting, success, failure, retry, and duplicate-submission behavior explicitly. Keep submission state separate from field validity and dirty state.
- Validate before mapping and submitting. Map the form value to the request DTO at the boundary rather than coupling controls to an API payload.
- Preserve useful user input on failure, expose actionable errors, and define whether a later submission supersedes or queues an earlier one.
Choosing or Migrating APIs
- Read the local form imports, templates, helper abstractions, and tests before choosing Signal Forms, Reactive Forms, or another approach.
- For a new form, use the approach that best matches the feature's validation, dynamic-control, async, and integration needs within the installed version.
- For a migration, preserve behavior and error semantics first. A conversion to
form()and[formField]is appropriate only when the repository version supports Signal Forms and the migration is part of the task; it is not a global cleanup rule. - Keep form-specific logic testable at the appropriate boundary. Test the user's visible validation and submission behavior, not framework internals.