mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(forms): use consistent error format returned from parse
Aligns the errors returned from the `parse` function in
`transformedValue` to use the same convention as the rest of signal
forms (a property called `error` that can contain a single error or list
of errors)
(cherry picked from commit 23fd8fa586)
This commit is contained in:
parent
e99b92a1f0
commit
bdfb60f3e3
6 changed files with 25 additions and 8 deletions
|
|
@ -435,7 +435,7 @@ export type OneOrMany<T> = T | readonly T[];
|
|||
|
||||
// @public
|
||||
export interface ParseResult<TValue> {
|
||||
readonly errors?: readonly ValidationError.WithoutFieldTree[];
|
||||
readonly error?: OneOrMany<ValidationError.WithoutFieldTree>;
|
||||
readonly value?: TValue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,3 +58,17 @@ export function isEmpty(value: unknown): boolean {
|
|||
}
|
||||
return value === '' || value === false || value == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes validation errors (which can be a single error, an array of errors, or undefined)
|
||||
* into a list of errors.
|
||||
*/
|
||||
export function normalizeErrors<T>(error: OneOrMany<T> | undefined): readonly T[] {
|
||||
if (error === undefined) {
|
||||
return [];
|
||||
}
|
||||
if (Array.isArray(error)) {
|
||||
return error as readonly T[];
|
||||
}
|
||||
return [error as T];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import {FORM_FIELD_PARSE_ERRORS} from '../directive/parse_errors';
|
||||
import {createParser} from '../util/parser';
|
||||
import type {ValidationError} from './rules';
|
||||
import type {OneOrMany} from './types';
|
||||
|
||||
/**
|
||||
* Result of parsing a raw value into a model value.
|
||||
|
|
@ -28,7 +29,7 @@ export interface ParseResult<TValue> {
|
|||
/**
|
||||
* Errors encountered during parsing, if any.
|
||||
*/
|
||||
readonly errors?: readonly ValidationError.WithoutFieldTree[];
|
||||
readonly error?: OneOrMany<ValidationError.WithoutFieldTree>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,7 +43,7 @@ export interface TransformedValueOptions<TValue, TRaw> {
|
|||
*
|
||||
* Should return an object containing the parsed result, which may contain:
|
||||
* - `value`: The parsed model value. If `undefined`, the model will not be updated.
|
||||
* - `errors`: Any parse errors encountered. If `undefined`, no errors are reported.
|
||||
* - `error`: Any parse errors encountered. If `undefined`, no errors are reported.
|
||||
*/
|
||||
parse: (rawValue: TRaw) => ParseResult<TValue>;
|
||||
|
||||
|
|
@ -93,7 +94,7 @@ export interface TransformedValueSignal<TRaw> extends WritableSignal<TRaw> {
|
|||
* if (val === '') return {value: null};
|
||||
* const num = Number(val);
|
||||
* if (Number.isNaN(num)) {
|
||||
* return {errors: [{kind: 'parse', message: `${val} is not numeric`}]};
|
||||
* return {error: {kind: 'parse', message: `${val} is not numeric`}};
|
||||
* }
|
||||
* return {value: num};
|
||||
* },
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export function getNativeControlValue(
|
|||
|
||||
if (element.validity.badInput) {
|
||||
return {
|
||||
errors: [new NativeInputParseError() as WithoutFieldTree<NativeInputParseError>],
|
||||
error: new NativeInputParseError() as WithoutFieldTree<NativeInputParseError>,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {type Signal, linkedSignal} from '@angular/core';
|
||||
import type {ValidationError} from '../api/rules';
|
||||
import {normalizeErrors} from '../api/rules/validation/util';
|
||||
import type {ParseResult} from '../api/transformed_value';
|
||||
|
||||
/**
|
||||
|
|
@ -44,12 +45,13 @@ export function createParser<TValue, TRaw>(
|
|||
|
||||
const setRawValue = (rawValue: TRaw) => {
|
||||
const result = parse(rawValue);
|
||||
errors.set(normalizeErrors(result.error));
|
||||
if (result.value !== undefined) {
|
||||
setValue(result.value);
|
||||
}
|
||||
// `errors` is a linked signal sourced from the model value; write parse errors after
|
||||
// model updates so `{value, errors}` results do not get reset by the recomputation.
|
||||
errors.set(result.errors ?? []);
|
||||
errors.set(normalizeErrors(result.error));
|
||||
};
|
||||
|
||||
return {errors: errors.asReadonly(), setRawValue};
|
||||
|
|
|
|||
|
|
@ -345,10 +345,10 @@ class TestNumberInput implements FormValueControl<number | null> {
|
|||
if (rawValue === '') return {value: null};
|
||||
const value = Number(rawValue);
|
||||
if (Number.isNaN(value)) {
|
||||
return {errors: [{kind: 'parse', message: `${rawValue} is not numeric`}]};
|
||||
return {error: {kind: 'parse', message: `${rawValue} is not numeric`}};
|
||||
}
|
||||
if (this.parseMax() != null && value > this.parseMax()!) {
|
||||
return {value, errors: [maxError(this.parseMax()!)]};
|
||||
return {value, error: [maxError(this.parseMax()!)]};
|
||||
}
|
||||
return {value};
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue