Skip to content

Commit 34dd3f6

Browse files
committed
refactor(forms): rename Field to FieldTree (angular#64214)
There are two primary reasons for this renaming: 1. It better reflects the actual nature of the proxy object, namely that the form is represented as a tree, and that this object is used for navigating the tree structure (while `FieldState` is used for getting the state at a particular point in the structure). 2. This frees up the name `Field` to be used for the directive that binds a `FieldTree` to a UI control. PR Close angular#64214
1 parent e0b9d9b commit 34dd3f6

File tree

15 files changed

+107
-107
lines changed

15 files changed

+107
-107
lines changed

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ export class Control<T> {
7373
get cva(): ControlValueAccessor | undefined;
7474
readonly cvaArray: ControlValueAccessor[] | null;
7575
readonly el: ElementRef<HTMLElement>;
76-
readonly field: i0.WritableSignal<Field<T>>;
76+
readonly field: i0.WritableSignal<FieldTree<T>>;
7777
// (undocumented)
78-
set _field(value: Field<T>);
78+
set _field(value: FieldTree<T>);
7979
get ngControl(): NgControl;
8080
readonly state: i0.Signal<FieldState<T, string | number>>;
8181
// (undocumented)
@@ -97,7 +97,7 @@ export function customError<E extends Partial<ValidationError>>(obj?: E): Withou
9797
export class CustomValidationError implements ValidationError {
9898
constructor(options?: ValidationErrorOptions);
9999
[key: PropertyKey]: unknown;
100-
readonly field: Field<unknown>;
100+
readonly field: FieldTree<unknown>;
101101
readonly kind: string;
102102
readonly message?: string;
103103
}
@@ -107,7 +107,7 @@ export function disabled<TValue, TPathKind extends PathKind = PathKind.Root>(pat
107107

108108
// @public
109109
export interface DisabledReason {
110-
readonly field: Field<unknown>;
110+
readonly field: FieldTree<unknown>;
111111
readonly message?: string;
112112
}
113113

@@ -126,9 +126,6 @@ export class EmailValidationError extends _NgValidationError {
126126
readonly kind = "email";
127127
}
128128

129-
// @public
130-
export type Field<TValue, TKey extends string | number = string | number> = (() => FieldState<TValue, TKey>) & (TValue extends Array<infer U> ? ReadonlyArrayLike<MaybeField<U, number>> : TValue extends Record<string, any> ? Subfields<TValue> : unknown);
131-
132129
// @public
133130
export type FieldContext<TValue, TPathKind extends PathKind = PathKind.Root> = TPathKind extends PathKind.Item ? ItemFieldContext<TValue> : TPathKind extends PathKind.Child ? ChildFieldContext<TValue> : RootFieldContext<TValue>;
134131

@@ -165,20 +162,23 @@ export interface FieldState<TValue, TKey extends string | number = string | numb
165162
readonly value: WritableSignal<TValue>;
166163
}
167164

165+
// @public
166+
export type FieldTree<TValue, TKey extends string | number = string | number> = (() => FieldState<TValue, TKey>) & (TValue extends Array<infer U> ? ReadonlyArrayLike<MaybeFieldTree<U, number>> : TValue extends Record<string, any> ? Subfields<TValue> : unknown);
167+
168168
// @public
169169
export type FieldValidationResult<E extends ValidationError = ValidationError> = ValidationSuccess | OneOrMany<WithoutField<E>>;
170170

171171
// @public
172172
export type FieldValidator<TValue, TPathKind extends PathKind = PathKind.Root> = LogicFn<TValue, FieldValidationResult, TPathKind>;
173173

174174
// @public
175-
export function form<TValue>(model: WritableSignal<TValue>): Field<TValue>;
175+
export function form<TValue>(model: WritableSignal<TValue>): FieldTree<TValue>;
176176

177177
// @public
178-
export function form<TValue>(model: WritableSignal<TValue>, schemaOrOptions: SchemaOrSchemaFn<TValue> | FormOptions): Field<TValue>;
178+
export function form<TValue>(model: WritableSignal<TValue>, schemaOrOptions: SchemaOrSchemaFn<TValue> | FormOptions): FieldTree<TValue>;
179179

180180
// @public
181-
export function form<TValue>(model: WritableSignal<TValue>, schema: SchemaOrSchemaFn<TValue>, options: FormOptions): Field<TValue>;
181+
export function form<TValue>(model: WritableSignal<TValue>, schema: SchemaOrSchemaFn<TValue>, options: FormOptions): FieldTree<TValue>;
182182

183183
// @public
184184
export interface FormCheckboxControl extends FormUiControl {
@@ -295,10 +295,10 @@ export class MaxValidationError extends _NgValidationError {
295295
}
296296

297297
// @public
298-
export type MaybeField<TValue, TKey extends string | number = string | number> = (TValue & undefined) | Field<Exclude<TValue, undefined>, TKey>;
298+
export type MaybeFieldPath<TValue, TPathKind extends PathKind = PathKind.Root> = (TValue & undefined) | FieldPath<Exclude<TValue, undefined>, TPathKind>;
299299

300300
// @public
301-
export type MaybeFieldPath<TValue, TPathKind extends PathKind = PathKind.Root> = (TValue & undefined) | FieldPath<Exclude<TValue, undefined>, TPathKind>;
301+
export type MaybeFieldTree<TValue, TKey extends string | number = string | number> = (TValue & undefined) | FieldTree<Exclude<TValue, undefined>, TKey>;
302302

303303
// @public
304304
export const MIN: AggregateProperty<number | undefined, number | undefined>;
@@ -445,8 +445,8 @@ export class RequiredValidationError extends _NgValidationError {
445445

446446
// @public
447447
export interface RootFieldContext<TValue> {
448-
readonly field: Field<TValue>;
449-
readonly fieldOf: <P>(p: FieldPath<P>) => Field<P>;
448+
readonly field: FieldTree<TValue>;
449+
readonly fieldOf: <P>(p: FieldPath<P>) => FieldTree<P>;
450450
readonly state: FieldState<TValue>;
451451
readonly stateOf: <P>(p: FieldPath<P>) => FieldState<P>;
452452
readonly value: Signal<TValue>;
@@ -484,11 +484,11 @@ export class StandardSchemaValidationError extends _NgValidationError {
484484

485485
// @public
486486
export type Subfields<TValue> = {
487-
readonly [K in keyof TValue as TValue[K] extends Function ? never : K]: MaybeField<TValue[K], string>;
487+
readonly [K in keyof TValue as TValue[K] extends Function ? never : K]: MaybeFieldTree<TValue[K], string>;
488488
};
489489

490490
// @public
491-
export function submit<TValue>(form: Field<TValue>, action: (form: Field<TValue>) => Promise<TreeValidationResult>): Promise<void>;
491+
export function submit<TValue>(form: FieldTree<TValue>, action: (form: FieldTree<TValue>) => Promise<TreeValidationResult>): Promise<void>;
492492

493493
// @public
494494
export type SubmittedStatus = 'unsubmitted' | 'submitted' | 'submitting';
@@ -516,7 +516,7 @@ export function validateTree<TValue, TPathKind extends PathKind = PathKind.Root>
516516

517517
// @public
518518
export interface ValidationError {
519-
readonly field: Field<unknown>;
519+
readonly field: FieldTree<unknown>;
520520
readonly kind: string;
521521
readonly message?: string;
522522
}
@@ -532,12 +532,12 @@ export type Validator<TValue, TPathKind extends PathKind = PathKind.Root> = Logi
532532

533533
// @public
534534
export type WithField<T> = T & {
535-
field: Field<unknown>;
535+
field: FieldTree<unknown>;
536536
};
537537

538538
// @public
539539
export type WithOptionalField<T> = Omit<T, 'field'> & {
540-
field?: Field<unknown>;
540+
field?: FieldTree<unknown>;
541541
};
542542

543543
// @public

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,14 @@ export interface FormUiControl {
104104
}
105105

106106
/**
107-
* A contract for a form control that edits a `Field` of type `TValue`. Any component that
107+
* A contract for a form control that edits a `FieldTree` of type `TValue`. Any component that
108108
* implements this contract can be used with the `Control` directive.
109109
*
110110
* Many of the properties declared on this contract are optional. They do not need to be
111111
* implemented, but if they are will be kept in sync with the field state of the field bound to the
112112
* `Control` directive.
113113
*
114-
* @template TValue The type of `Field` that the implementing component can edit.
114+
* @template TValue The type of `FieldTree` that the implementing component can edit.
115115
*
116116
* @category control
117117
* @experimental 21.0.0
@@ -120,7 +120,7 @@ export interface FormValueControl<TValue> extends FormUiControl {
120120
/**
121121
* The value is the only required property in this contract. A component that wants to integrate
122122
* with the `Control` directive via this contract, *must* provide a `model()` that will be kept in
123-
* sync with the value of the bound `Field`.
123+
* sync with the value of the bound `FieldTree`.
124124
*/
125125
readonly value: ModelSignal<TValue>;
126126
// TODO: We currently require that a `checked` input not be present, as we may want to introduce a
@@ -135,7 +135,7 @@ export interface FormValueControl<TValue> extends FormUiControl {
135135
}
136136

137137
/**
138-
* A contract for a form control that edits a boolean checkbox `Field`. Any component that
138+
* A contract for a form control that edits a boolean checkbox `FieldTree`. Any component that
139139
* implements this contract can be used with the `Control` directive.
140140
*
141141
* Many of the properties declared on this contract are optional. They do not need to be
@@ -149,7 +149,7 @@ export interface FormCheckboxControl extends FormUiControl {
149149
/**
150150
* The checked is the only required property in this contract. A component that wants to integrate
151151
* with the `Control` directive, *must* provide a `model()` that will be kept in sync with the
152-
* value of the bound `Field`.
152+
* value of the bound `FieldTree`.
153153
*/
154154
readonly checked: ModelSignal<boolean>;
155155
// TODO: maybe this doesn't have to be strictly `undefined`? It just can't be a model signal.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
} from '../util/private';
4141
import {FormCheckboxControl, FormUiControl, FormValueControl} from './control';
4242
import {AggregateProperty, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN, REQUIRED} from './property';
43-
import type {Field} from './types';
43+
import type {FieldTree} from './types';
4444

4545
/**
4646
* Lightweight DI token provided by the {@link Control} directive.
@@ -50,7 +50,7 @@ export const CONTROL = new InjectionToken<Control<unknown>>(
5050
);
5151

5252
/**
53-
* Binds a form `Field` to a UI control that edits it. A UI control can be one of several things:
53+
* Binds a form `FieldTree` to a UI control that edits it. A UI control can be one of several things:
5454
* 1. A native HTML input or textarea
5555
* 2. A signal forms custom control that implements `FormValueControl` or `FormCheckboxControl`
5656
* 3. A component that provides a ControlValueAccessor. This should only be used to backwards
@@ -89,7 +89,7 @@ export class Control<T> {
8989
private initialized = false;
9090

9191
/** The field that is bound to this control. */
92-
readonly field = signal<Field<T>>(undefined as any);
92+
readonly field = signal<FieldTree<T>>(undefined as any);
9393

9494
// If `[control]` is applied to a custom UI control, it wants to synchronize state in the field w/
9595
// the inputs of that custom control. This is difficult to do in user-land. We use `effect`, but
@@ -103,7 +103,7 @@ export class Control<T> {
103103
// before the important lifecycle hooks of the UI control. We can then initialize all our effects
104104
// and force them to run immediately, ensuring all required inputs have values.
105105
@Input({required: true, alias: 'control'})
106-
set _field(value: Field<T>) {
106+
set _field(value: FieldTree<T>) {
107107
this.field.set(value);
108108
if (!this.initialized) {
109109
this.initialize();

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

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import {FieldPathNode} from '../schema/path_node';
1616
import {assertPathIsCurrent, isSchemaOrSchemaFn, SchemaImpl} from '../schema/schema';
1717
import {isArray} from '../util/type_guards';
1818
import type {
19-
Field,
2019
FieldPath,
20+
FieldTree,
2121
LogicFn,
2222
OneOrMany,
2323
PathKind,
@@ -73,8 +73,8 @@ function normalizeFormArgs<TValue>(
7373
}
7474

7575
/**
76-
* Creates a form wrapped around the given model data. A form is represented as simply a `Field` of
77-
* the model data.
76+
* Creates a form wrapped around the given model data. A form is represented as simply a `FieldTree`
77+
* of the model data.
7878
*
7979
* `form` uses the given model as the source of truth and *does not* maintain its own copy of the
8080
* data. This means that updating the value on a `FieldState` updates the originally passed in model
@@ -92,17 +92,17 @@ function normalizeFormArgs<TValue>(
9292
* @param model A writable signal that contains the model data for the form. The resulting field
9393
* structure will match the shape of the model and any changes to the form data will be written to
9494
* the model.
95-
* @return A `Field` representing a form around the data model.
95+
* @return A `FieldTree` representing a form around the data model.
9696
* @template TValue The type of the data model.
9797
*
9898
* @category structure
9999
* @experimental 21.0.0
100100
*/
101-
export function form<TValue>(model: WritableSignal<TValue>): Field<TValue>;
101+
export function form<TValue>(model: WritableSignal<TValue>): FieldTree<TValue>;
102102

103103
/**
104-
* Creates a form wrapped around the given model data. A form is represented as simply a `Field` of
105-
* the model data.
104+
* Creates a form wrapped around the given model data. A form is represented as simply a `FieldTree`
105+
* of the model data.
106106
*
107107
* `form` uses the given model as the source of truth and *does not* maintain its own copy of the
108108
* data. This means that updating the value on a `FieldState` updates the originally passed in model
@@ -139,7 +139,7 @@ export function form<TValue>(model: WritableSignal<TValue>): Field<TValue>;
139139
* 1. A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.).
140140
* When passing a schema, the form options can be passed as a third argument if needed.
141141
* 2. The form options
142-
* @return A `Field` representing a form around the data model
142+
* @return A `FieldTree` representing a form around the data model
143143
* @template TValue The type of the data model.
144144
*
145145
* @category structure
@@ -148,11 +148,11 @@ export function form<TValue>(model: WritableSignal<TValue>): Field<TValue>;
148148
export function form<TValue>(
149149
model: WritableSignal<TValue>,
150150
schemaOrOptions: SchemaOrSchemaFn<TValue> | FormOptions,
151-
): Field<TValue>;
151+
): FieldTree<TValue>;
152152

153153
/**
154-
* Creates a form wrapped around the given model data. A form is represented as simply a `Field` of
155-
* the model data.
154+
* Creates a form wrapped around the given model data. A form is represented as simply a `FieldTree`
155+
* of the model data.
156156
*
157157
* `form` uses the given model as the source of truth and *does not* maintain its own copy of the
158158
* data. This means that updating the value on a `FieldState` updates the originally passed in model
@@ -187,7 +187,7 @@ export function form<TValue>(
187187
* the model.
188188
* @param schema A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.)
189189
* @param options The form options
190-
* @return A `Field` representing a form around the data model.
190+
* @return A `FieldTree` representing a form around the data model.
191191
* @template TValue The type of the data model.
192192
*
193193
* @category structure
@@ -197,9 +197,9 @@ export function form<TValue>(
197197
model: WritableSignal<TValue>,
198198
schema: SchemaOrSchemaFn<TValue>,
199199
options: FormOptions,
200-
): Field<TValue>;
200+
): FieldTree<TValue>;
201201

202-
export function form<TValue>(...args: any[]): Field<TValue> {
202+
export function form<TValue>(...args: any[]): FieldTree<TValue> {
203203
const [model, schema, options] = normalizeFormArgs<TValue>(args);
204204
const injector = options?.injector ?? inject(Injector);
205205
const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
@@ -208,7 +208,7 @@ export function form<TValue>(...args: any[]): Field<TValue> {
208208
const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
209209
fieldManager.createFieldManagementEffect(fieldRoot.structure);
210210

211-
return fieldRoot.fieldProxy as Field<TValue>;
211+
return fieldRoot.fieldProxy as FieldTree<TValue>;
212212
}
213213

214214
/**
@@ -225,8 +225,8 @@ export function form<TValue>(...args: any[]): Field<TValue> {
225225
* });
226226
* ```
227227
*
228-
* When binding logic to the array items, the `Field` for the array item is passed as an additional
229-
* argument. This can be used to reference other properties on the item.
228+
* When binding logic to the array items, the `FieldTree` for the array item is passed as an
229+
* additional argument. This can be used to reference other properties on the item.
230230
*
231231
* @example
232232
* ```
@@ -358,14 +358,14 @@ export function applyWhenValue(
358358
}
359359

360360
/**
361-
* Submits a given `Field` using the given action function and applies any server errors resulting
362-
* from the action to the field. Server errors returned by the `action` will be integrated into the
363-
* field as a `ValidationError` on the sub-field indicated by the `field` property of the server
364-
* error.
361+
* Submits a given `FieldTree` using the given action function and applies any server errors
362+
* resulting from the action to the field. Server errors returned by the `action` will be integrated
363+
* into the field as a `ValidationError` on the sub-field indicated by the `field` property of the
364+
* server error.
365365
*
366366
* @example
367367
* ```
368-
* async function registerNewUser(registrationForm: Field<{username: string, password: string}>) {
368+
* async function registerNewUser(registrationForm: FieldTree<{username: string, password: string}>) {
369369
* const result = await myClient.registerNewUser(registrationForm().value());
370370
* if (result.errorCode === myClient.ErrorCode.USERNAME_TAKEN) {
371371
* return [{
@@ -392,8 +392,8 @@ export function applyWhenValue(
392392
* @experimental 21.0.0
393393
*/
394394
export async function submit<TValue>(
395-
form: Field<TValue>,
396-
action: (form: Field<TValue>) => Promise<TreeValidationResult>,
395+
form: FieldTree<TValue>,
396+
action: (form: FieldTree<TValue>) => Promise<TreeValidationResult>,
397397
) {
398398
const node = form() as FieldNode;
399399
markAllAsTouched(node);
@@ -445,7 +445,7 @@ function setServerErrors(
445445
* Creates a `Schema` that adds logic rules to a form.
446446
* @param fn A **non-reactive** function that sets up reactive logic rules for the form.
447447
* @returns A schema object that implements the given logic.
448-
* @template TValue The value type of a `Field` that this schema binds to.
448+
* @template TValue The value type of a `FieldTree` that this schema binds to.
449449
*
450450
* @category structure
451451
* @experimental 21.0.0

0 commit comments

Comments
 (0)