Skip to content

Commit dd208ca

Browse files
mmalerbaleonsenft
authored andcommitted
feat(forms): update submit function to accept options object
Changes the `submit` function signature to accept a `FormSubmitOptions` object instead of a direct action callback. This allows for more flexibility, including: - `action`: The standard submit action to perform with the data. - `onInvalid`: A callback to execute when the submit action is not triggered due to failing validation - `ignoreValidators`: Controls whether pending validators or invalid validators should be ignored Also updates the return value of `submit` to a `Promise<boolean` to indicate submission success.
1 parent 43d61ec commit dd208ca

File tree

5 files changed

+254
-100
lines changed

5 files changed

+254
-100
lines changed

goldens/public-api/forms/signals/compat/index.api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import { AbstractControl } from '@angular/forms';
88
import * as _angular_forms from '@angular/forms';
99
import { ControlValueAccessor } from '@angular/forms';
10-
import { DestroyableInjector } from '@angular/core';
1110
import { EventEmitter } from '@angular/core';
1211
import { FormControlState } from '@angular/forms';
1312
import { FormControlStatus } from '@angular/forms';

goldens/public-api/forms/signals/index.api.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import { AbstractControl } from '@angular/forms';
88
import * as _angular_forms from '@angular/forms';
99
import { ControlValueAccessor } from '@angular/forms';
10-
import { DestroyableInjector } from '@angular/core';
1110
import { FormControlStatus } from '@angular/forms';
1211
import { HttpResourceOptions } from '@angular/common/http';
1312
import { HttpResourceRequest } from '@angular/common/http';
@@ -210,12 +209,17 @@ export interface FormFieldBindingOptions {
210209

211210
// @public
212211
export interface FormOptions {
213-
adapter?: FieldAdapter;
214212
injector?: Injector;
215-
// (undocumented)
216213
name?: string;
217214
}
218215

216+
// @public
217+
export interface FormSubmitOptions<TModel> {
218+
action: (form: FieldTree<TModel>) => Promise<TreeValidationResult>;
219+
ignoreValidators?: 'pending' | 'none' | 'all';
220+
onInvalid?: (form: FieldTree<TModel>) => void;
221+
}
222+
219223
// @public
220224
export interface FormUiControl<TValue> {
221225
readonly dirty?: InputSignal<boolean> | InputSignalWithTransform<boolean, unknown>;
@@ -555,7 +559,7 @@ export type Subfields<TModel> = {
555559
};
556560

557561
// @public
558-
export function submit<TModel>(form: FieldTree<TModel>, action: (form: FieldTree<TModel>) => Promise<TreeValidationResult>): Promise<void>;
562+
export function submit<TModel>(form: FieldTree<TModel>, options: FormSubmitOptions<TModel>): Promise<boolean>;
559563

560564
// @public
561565
export type TreeValidationResult<E extends ValidationError.WithOptionalFieldTree = ValidationError.WithOptionalFieldTree> = ValidationSuccess | OneOrMany<E>;

packages/forms/signals/src/api/structure.ts

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ import type {
3131
TreeValidationResult,
3232
} from './types';
3333

34+
/**
35+
* Options that can be specified when submitting a form.
36+
*
37+
* @experimental 21.2.0
38+
*/
39+
export interface FormSubmitOptions<TModel> {
40+
/** Function to run when submitting the form data (when form is valid). */
41+
action: (form: FieldTree<TModel>) => Promise<TreeValidationResult>;
42+
/** Function to run when attempting to submit the form data but validation is failing. */
43+
onInvalid?: (form: FieldTree<TModel>) => void;
44+
/**
45+
* Whether to ignore any of the validators when submitting:
46+
* - 'pending': Will submit if there are no invalid validators, pending validators do not block submission (default)
47+
* - 'none': Will not submit unless all validators are passing, pending validators block submission
48+
* - 'ignore': Will always submit regardless of invalid or pending validators
49+
*/
50+
ignoreValidators?: 'pending' | 'none' | 'all';
51+
}
52+
3453
/**
3554
* Options that may be specified when creating a form.
3655
*
@@ -43,11 +62,13 @@ export interface FormOptions {
4362
* current [injection context](guide/di/dependency-injection-context), will be used.
4463
*/
4564
injector?: Injector;
65+
/** The name of the root form, used in generating name attributes for the fields. */
4666
name?: string;
4767

4868
/**
4969
* Adapter allows managing fields in a more flexible way.
5070
* Currently this is used to support interop with reactive forms.
71+
* @internal
5172
*/
5273
adapter?: FieldAdapter;
5374
}
@@ -350,42 +371,56 @@ export function applyWhenValue(
350371
* }
351372
*
352373
* const registrationForm = form(signal({username: 'god', password: ''}));
353-
* submit(registrationForm, async (f) => {
354-
* return registerNewUser(registrationForm);
374+
* submit(registrationForm, {
375+
* action: async (f) => {
376+
* return registerNewUser(registrationForm);
377+
* }
355378
* });
356379
* registrationForm.username().errors(); // [{kind: 'server', message: 'Username already taken'}]
357380
* ```
358381
*
359382
* @param form The field to submit.
360-
* @param action An asynchronous action used to submit the field. The action may return submission
361-
* errors.
383+
* @param options Options for the submission.
384+
* @returns Whether the submission was successful.
362385
* @template TModel The data type of the field being submitted.
363386
*
364387
* @category submission
365388
* @experimental 21.0.0
366389
*/
367390
export async function submit<TModel>(
368391
form: FieldTree<TModel>,
369-
action: (form: FieldTree<TModel>) => Promise<TreeValidationResult>,
370-
) {
371-
const node = form() as unknown as FieldNode;
372-
const invalid = untracked(() => {
392+
options: FormSubmitOptions<TModel>,
393+
): Promise<boolean> {
394+
return untracked(async () => {
395+
const {action, onInvalid} = options;
396+
const ignoreValidators = options.ignoreValidators ?? 'pending';
397+
const node = form() as unknown as FieldNode;
398+
373399
markAllAsTouched(node);
374-
return node.invalid();
375-
});
376400

377-
// Fail fast if the form is already invalid.
378-
if (invalid) {
379-
return;
380-
}
401+
// Determine whether or not to run the action based on the current validity.
402+
let shouldRunAction = true;
403+
if (ignoreValidators === 'none') {
404+
shouldRunAction = node.valid();
405+
} else if (ignoreValidators === 'pending') {
406+
shouldRunAction = !node.invalid();
407+
}
381408

382-
node.submitState.selfSubmitting.set(true);
383-
try {
384-
const errors = await action(form);
385-
errors && setSubmissionErrors(node, errors);
386-
} finally {
387-
node.submitState.selfSubmitting.set(false);
388-
}
409+
// Run the action (or alternatively the `onInvalid` callback)
410+
try {
411+
if (shouldRunAction) {
412+
node.submitState.selfSubmitting.set(true);
413+
const errors = await action(form);
414+
errors && setSubmissionErrors(node, errors);
415+
return !errors || (isArray(errors) && errors.length === 0);
416+
} else if (onInvalid) {
417+
onInvalid(form);
418+
}
419+
return false;
420+
} finally {
421+
node.submitState.selfSubmitting.set(false);
422+
}
423+
});
389424
}
390425

391426
/**

packages/forms/signals/test/node/compat/compat.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,10 @@ describe('Forms compat', () => {
347347

348348
const {promise, resolve} = promiseWithResolvers<TreeValidationResult>();
349349

350-
const result = submit(f as unknown as FieldTree<void>, () => {
351-
return promise;
350+
const result = submit(f as unknown as FieldTree<void>, {
351+
action: () => {
352+
return promise;
353+
},
352354
});
353355

354356
expect(f().submitting()).toBe(true);

0 commit comments

Comments
 (0)