|
6 | 6 | * found in the LICENSE file at https://angular.dev/license |
7 | 7 | */ |
8 | 8 |
|
9 | | -import {DebounceTimer, Resource, Signal, computed, debounced, ɵchain} from '@angular/core'; |
| 9 | +import { |
| 10 | + DebounceTimer, |
| 11 | + Resource, |
| 12 | + resource, |
| 13 | + Signal, |
| 14 | + computed, |
| 15 | + debounced, |
| 16 | + ɵchain, |
| 17 | +} from '@angular/core'; |
10 | 18 | import {FieldNode} from '../../../field/node'; |
11 | 19 | import {addDefaultField} from '../../../field/validation'; |
12 | 20 | import {FieldPathNode} from '../../../schema/path_node'; |
13 | 21 | import {assertPathIsCurrent} from '../../../schema/schema'; |
14 | 22 | import { |
15 | 23 | FieldContext, |
| 24 | + FieldValidatorAsync, |
16 | 25 | LogicFn, |
17 | 26 | PathKind, |
18 | 27 | SchemaPath, |
@@ -117,19 +126,167 @@ export interface AsyncValidatorOptions< |
117 | 126 | * @param opts The async validation options. |
118 | 127 | * @template TValue The type of value stored in the field being validated. |
119 | 128 | * @template TParams The type of parameters to the resource. |
120 | | - * @template TResult The type of result returned by the resource |
121 | | - * @template TPathKind The kind of path being validated (a root path, child path, or item of an array) |
| 129 | + * @template TResult The type of result returned by the resource. |
| 130 | + * @template TPathKind The kind of path being validated (a root path, child path, or item of an array). |
122 | 131 | * |
123 | 132 | * @see [Signal Form Async Validation](guide/forms/signals/validation#async-validation) |
124 | 133 | * @see [Custom async validation](guide/forms/signals/async-operations#custom-async-validation-with-validateasync) |
| 134 | + * @see [Custom validation rules](guide/forms/signals/validation#using-validateasync) |
125 | 135 | * @category validation |
126 | 136 | * @publicApi 22.0 |
127 | 137 | */ |
128 | 138 | export function validateAsync<TValue, TParams, TResult, TPathKind extends PathKind = PathKind.Root>( |
129 | 139 | path: SchemaPath<TValue, SchemaPathRules.Supported, TPathKind>, |
130 | 140 | opts: AsyncValidatorOptions<TValue, TParams, TResult, TPathKind>, |
| 141 | +): void; |
| 142 | + |
| 143 | +/** |
| 144 | + * Adds async validation to the field corresponding to the given path using a simple validator function. |
| 145 | + * |
| 146 | + * @param path A path indicating the field to bind the async validation logic to. |
| 147 | + * @param logic A validator function that returns the validation errors asynchronously. |
| 148 | + * @param config Optional, allows providing any of the following options for the async validation over the simple validator function: |
| 149 | + * - `debounce`: Duration in milliseconds to wait before triggering the async operation, or a function that |
| 150 | + * returns a promise that resolves when the update should proceed. |
| 151 | + * - `when`: A function that receives the field context and returns true if the async validation should be run. |
| 152 | + * - `onError`: A function to handle errors thrown by the async validator (HTTP errors, network errors, etc.). |
| 153 | + * Receives the error and the field context, returns a list of validation errors. |
| 154 | + * @template TValue The type of value stored in the field being validated. |
| 155 | + * @template TPathKind The kind of path being validated (a root path, child path, or item of an array). |
| 156 | + * |
| 157 | + * @see [Signal Form Async Validation](guide/forms/signals/validation#async-validation) |
| 158 | + * @see [Custom async validation](guide/forms/signals/async-operations#custom-async-validation-with-validateasync) |
| 159 | + * @see [Custom validation rules](guide/forms/signals/validation#using-validateasync) |
| 160 | + * @category validation |
| 161 | + * @publicApi 22.0 |
| 162 | + */ |
| 163 | +export function validateAsync<TValue, TPathKind extends PathKind = PathKind.Root>( |
| 164 | + path: SchemaPath<TValue, SchemaPathRules.Supported, TPathKind>, |
| 165 | + logic: NoInfer<FieldValidatorAsync<TValue, TPathKind>>, |
| 166 | + config?: { |
| 167 | + debounce?: DebounceTimer<FieldContext<TValue, TPathKind> | undefined>; |
| 168 | + when?: NoInfer<LogicFn<TValue, boolean, TPathKind>>; |
| 169 | + onError?: (error: unknown, ctx: FieldContext<TValue, TPathKind>) => TreeValidationResult; |
| 170 | + }, |
| 171 | +): void; |
| 172 | + |
| 173 | +/** |
| 174 | + * Internal implementation for registering async validation on a field via resource options |
| 175 | + * or a simple validator function. |
| 176 | + * |
| 177 | + * @param path A path indicating the field to bind the async validation logic to. |
| 178 | + * @param optsOrLogic Either an object with full resource validator options or a simple async validator function. |
| 179 | + * @param config Optional, allows providing any of the following options for the async validation over the simple validator function: |
| 180 | + * - `debounce`: Duration in milliseconds to wait before triggering the async operation, or a function that |
| 181 | + * returns a promise that resolves when the update should proceed. |
| 182 | + * - `when`: A function that receives the field context and returns true if the async validation should be run. |
| 183 | + * - `onError`: A function to handle errors thrown by the async validator (HTTP errors, network errors, etc.). |
| 184 | + * Receives the error and the field context, returns a list of validation errors. |
| 185 | + * @template TValue The type of value stored in the field being validated. |
| 186 | + * @template TParams The type of parameters to the resource. |
| 187 | + * @template TResult The type of result returned by the resource. |
| 188 | + * @template TPathKind The kind of path being validated (a root path, child path, or item of an array). |
| 189 | + * |
| 190 | + * @internal |
| 191 | + */ |
| 192 | +export function validateAsync< |
| 193 | + TValue, |
| 194 | + TParams = unknown, |
| 195 | + TResult = unknown, |
| 196 | + TPathKind extends PathKind = PathKind.Root, |
| 197 | +>( |
| 198 | + path: SchemaPath<TValue, SchemaPathRules.Supported, TPathKind>, |
| 199 | + optsOrLogic: |
| 200 | + | AsyncValidatorOptions<TValue, TParams, TResult, TPathKind> |
| 201 | + | FieldValidatorAsync<TValue, TPathKind>, |
| 202 | + config?: { |
| 203 | + debounce?: DebounceTimer<FieldContext<TValue, TPathKind> | undefined>; |
| 204 | + when?: NoInfer<LogicFn<TValue, boolean, TPathKind>>; |
| 205 | + onError?: (error: unknown, ctx: FieldContext<TValue, TPathKind>) => TreeValidationResult; |
| 206 | + }, |
131 | 207 | ): void { |
132 | 208 | assertPathIsCurrent(path); |
| 209 | + |
| 210 | + if (typeof optsOrLogic === 'function') { |
| 211 | + const logic = optsOrLogic; |
| 212 | + const configOpts = config ?? {}; |
| 213 | + |
| 214 | + // Check if debounce option is a function, wrap it to pass the field context and last value. |
| 215 | + const debounce = |
| 216 | + typeof configOpts.debounce === 'function' |
| 217 | + ? ( |
| 218 | + value: {ctx: FieldContext<TValue, TPathKind>; value: TValue} | undefined, |
| 219 | + lastValue: unknown, |
| 220 | + ) => (configOpts.debounce as Function)(value?.ctx, lastValue) |
| 221 | + : configOpts.debounce; |
| 222 | + |
| 223 | + // Map the simple validator function to the full AsyncValidatorOptions structure. |
| 224 | + // Reading `ctx.value()` inside `params` ensures signal dependencies are tracked |
| 225 | + // so the resource loader re-runs whenever the field value changes. |
| 226 | + const mappedOpts: AsyncValidatorOptions< |
| 227 | + TValue, |
| 228 | + {ctx: FieldContext<TValue, TPathKind>; value: TValue} | undefined, |
| 229 | + TreeValidationResult | undefined, |
| 230 | + TPathKind |
| 231 | + > = { |
| 232 | + params: (ctx: FieldContext<TValue, TPathKind>) => ({ |
| 233 | + ctx, |
| 234 | + value: ctx.value(), |
| 235 | + }), |
| 236 | + debounce: debounce as DebounceTimer< |
| 237 | + {ctx: FieldContext<TValue, TPathKind>; value: TValue} | undefined |
| 238 | + >, |
| 239 | + factory: ( |
| 240 | + paramsSignal: Signal<{ctx: FieldContext<TValue, TPathKind>; value: TValue} | undefined>, |
| 241 | + ) => |
| 242 | + resource({ |
| 243 | + params: () => paramsSignal(), |
| 244 | + loader: async ({ |
| 245 | + params, |
| 246 | + }: { |
| 247 | + params: {ctx: FieldContext<TValue, TPathKind>; value: TValue} | undefined; |
| 248 | + }) => { |
| 249 | + if (!params) return undefined; |
| 250 | + const res = await logic(params.ctx); |
| 251 | + return res as TreeValidationResult | undefined; |
| 252 | + }, |
| 253 | + }), |
| 254 | + onSuccess: (result: TreeValidationResult | undefined) => result, |
| 255 | + onError: |
| 256 | + configOpts.onError ?? |
| 257 | + ((error: unknown) => ({ |
| 258 | + kind: 'asyncError', |
| 259 | + message: String(error ?? 'Async validation failed'), |
| 260 | + })), |
| 261 | + when: configOpts.when, |
| 262 | + }; |
| 263 | + |
| 264 | + registerAsyncResourceValidator(path, mappedOpts); |
| 265 | + return; |
| 266 | + } |
| 267 | + |
| 268 | + registerAsyncResourceValidator(path, optsOrLogic); |
| 269 | +} |
| 270 | + |
| 271 | +/** |
| 272 | + * Registers an async resource validator for the given path and options. |
| 273 | + * |
| 274 | + * @template TValue The type of value stored in the field being validated. |
| 275 | + * @template TParams The type of parameters to the resource. |
| 276 | + * @template TResult The type of result returned by the resource |
| 277 | + * @template TPathKind The kind of path being validated (a root path, child path, or item of an array) |
| 278 | + * @param path |
| 279 | + * @param opts |
| 280 | + */ |
| 281 | +function registerAsyncResourceValidator< |
| 282 | + TValue, |
| 283 | + TParams, |
| 284 | + TResult, |
| 285 | + TPathKind extends PathKind = PathKind.Root, |
| 286 | +>( |
| 287 | + path: SchemaPath<TValue, SchemaPathRules.Supported, TPathKind>, |
| 288 | + opts: AsyncValidatorOptions<TValue, TParams, TResult, TPathKind>, |
| 289 | +): void { |
133 | 290 | const pathNode = FieldPathNode.unwrapFieldPath(path); |
134 | 291 |
|
135 | 292 | const RESOURCE = createManagedMetadataKey<ReturnType<typeof opts.factory>, TParams | undefined>( |
|
0 commit comments