Form submission in dynamic forms integrates with Angular Signal Forms' native submit() function, providing automatic button disabling and loading states.
Overview
The submission system provides:
- Async form submission with automatic loading state
- Thrown action errors caught and logged
- Automatic submit button disabling during submission
- Configurable button disabled behavior
- Custom disabled logic via
FormStateCondition
Note: The submission mechanism is optional - you can still handle submission manually via the (submitted) output if you prefer.
Basic Usage
Configure form submission with the submission property:
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FieldTree } from '@angular/forms/signals';
import { FormConfig, InferFormValue } from '@ng-forge/dynamic-forms';
const formFields = {
fields: [
{ type: 'input', key: 'email', label: 'Email', value: '', required: true, email: true },
{ type: 'input', key: 'password', label: 'Password', value: '', required: true, props: { type: 'password' } },
{ type: 'submit', key: 'submit', label: 'Sign In' },
],
} as const satisfies FormConfig;
type LoginForm = InferFormValue<typeof formFields.fields>;
@Component({...})
export class LoginFormComponent {
private http = inject(HttpClient);
config = {
...formFields,
submission: {
action: (form: FieldTree<LoginForm>) => this.http.post('/api/login', form().value()),
},
};
}SubmissionConfig
The submission.action function receives the form's FieldTree instance and can return an Observable or Promise. Read the current value with form().value().
While the action is executing, the form is in a submitting state, enabling automatic button disabling and loading states. Errors thrown by the action are caught and logged by the library, and the form exits the submitting state.
Note: The action's resolved value is discarded. Returned validation results (such as TreeValidationResult) are not currently applied to form fields.
Server Error Handling
Returned validation results are not applied to fields, so handle server errors inside your action for now, for example by mapping them to your own notification or error state:
import { catchError, EMPTY } from 'rxjs';
submission: {
action: (form) => this.http.post('/api/register', form().value()).pipe(
catchError((error) => {
if (error.error?.code === 'EMAIL_EXISTS') {
this.notifications.error('Email already exists');
return EMPTY;
}
throw error; // Unexpected errors are caught and logged by the library
}),
),
}Button Disabled Behavior
Submit and next page buttons are automatically disabled based on form state. Configure defaults via options:
const config: FormConfig = {
fields: [...],
options: {
submitButton: {
disableWhenInvalid: true, // Disable when form is invalid (default: true)
disableWhileSubmitting: true, // Disable during submission (default: true)
},
nextButton: {
disableWhenPageInvalid: true, // Disable when current page is invalid (default: true)
disableWhileSubmitting: true, // Disable during submission (default: true)
}
}
};SubmitButtonOptions
| Option | Type | Default | Description |
|---|---|---|---|
disableWhenInvalid |
boolean |
true |
Disable when form is invalid |
disableWhileSubmitting |
boolean |
true |
Disable during form submission |
NextButtonOptions
| Option | Type | Default | Description |
|---|---|---|---|
disableWhenPageInvalid |
boolean |
true |
Disable when current page has invalid fields |
disableWhileSubmitting |
boolean |
true |
Disable during form submission |
Custom Button Logic
Override form-level defaults on individual buttons using the logic array:
{
type: 'submit',
key: 'submit',
label: 'Submit',
logic: [
{ type: 'disabled', condition: 'formInvalid' },
{ type: 'disabled', condition: 'formSubmitting' }
]
}Note: type: 'submit' is a UI-integration convenience type that pre-configures the button with FormSubmitEvent. Use type: 'button' with event: FormSubmitEvent for the core API.
FormStateCondition
Special condition strings for button logic:
| Condition | Description |
|---|---|
'formInvalid' |
Evaluates to true when the form is invalid |
'formSubmitting' |
Evaluates to true when form submission is in progress |
'pageInvalid' |
Evaluates to true when the current page has invalid fields |
Logic Precedence
Button disabled state is determined in this order:
- Explicit
disabled: true- Always wins - Field-level
logicarray - If present, overrides form-level defaults - Form-level options - Default behavior from
options.submitButtonoroptions.nextButton
Advanced Logic Example
Combine form state conditions with custom expressions:
{
type: 'submit',
key: 'submit',
label: 'Submit',
logic: [
// Disable when form is invalid
{ type: 'disabled', condition: 'formInvalid' },
// Disable when submitting
{ type: 'disabled', condition: 'formSubmitting' },
// Also disable if terms not accepted
{
type: 'disabled',
condition: {
type: 'fieldValue',
fieldPath: 'acceptTerms',
operator: 'equals',
value: false,
}
}
]
}Value Exclusion
By default, field values are excluded from the (submitted) output when the field is hidden, disabled, or readonly. This prevents stale or irrelevant data from being submitted.
Value exclusion supports a 3-tier configuration hierarchy: Field > Form > Global (the most specific level wins). Internal form state and two-way binding are unaffected.
// Disable hidden value exclusion for this form
const config: FormConfig = {
fields: [...],
options: {
excludeValueIfHidden: false,
}
};See the Value Exclusion page under Recipes for full documentation on configuration tiers, per-field overrides, and migration instructions.
Manual Submission
If you prefer manual control, use the (submitted) output instead:
@Component({
template: `<form [dynamic-form]="config" (submitted)="onSubmit($event)"></form>`,
})
export class MyFormComponent {
onSubmit(value: FormValue) {
this.http.post('/api/submit', value).subscribe();
}
}Next Steps
- Events: Dispatch and subscribe to form events from inside or outside the form
- Schema Validation: Add cross-field validation rules with Angular schemas or Zod
- Value Exclusion: Control which field values are included in submission output