Skip to content

Commit 6bd22df

Browse files
kirjscrisbeto
authored andcommitted
fix(forms): Support readonly arrays in signal forms
This would allow using `readonly Array<...>` in types (cherry picked from commit 282220d)
1 parent 86374b2 commit 6bd22df

File tree

3 files changed

+31
-7
lines changed

3 files changed

+31
-7
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export interface FieldState<TValue, TKey extends string | number = string | numb
182182
}
183183

184184
// @public
185-
export type FieldTree<TModel, TKey extends string | number = string | number> = (() => [TModel] extends [AbstractControl] ? CompatFieldState<TModel, TKey> : FieldState<TModel, TKey>) & ([TModel] extends [AbstractControl] ? object : [TModel] extends [Array<infer U>] ? ReadonlyArrayLike<MaybeFieldTree<U, number>> : TModel extends Record<string, any> ? Subfields<TModel> : object);
185+
export type FieldTree<TModel, TKey extends string | number = string | number> = (() => [TModel] extends [AbstractControl] ? CompatFieldState<TModel, TKey> : FieldState<TModel, TKey>) & ([TModel] extends [AbstractControl] ? object : [TModel] extends [ReadonlyArray<infer U>] ? ReadonlyArrayLike<MaybeFieldTree<U, number>> : TModel extends Record<string, any> ? Subfields<TModel> : object);
186186

187187
// @public
188188
export type FieldValidator<TValue, TPathKind extends PathKind = PathKind.Root> = LogicFn<TValue, ValidationResult<ValidationError.WithoutField>, TPathKind>;
@@ -513,7 +513,7 @@ export namespace SchemaPathRules {
513513
}
514514

515515
// @public
516-
export type SchemaPathTree<TModel, TPathKind extends PathKind = PathKind.Root> = ([TModel] extends [AbstractControl] ? CompatSchemaPath<TModel, TPathKind> : SchemaPath<TModel, SchemaPathRules.Supported, TPathKind>) & (TModel extends AbstractControl ? unknown : TModel extends Array<any> ? unknown : TModel extends Record<string, any> ? {
516+
export type SchemaPathTree<TModel, TPathKind extends PathKind = PathKind.Root> = ([TModel] extends [AbstractControl] ? CompatSchemaPath<TModel, TPathKind> : SchemaPath<TModel, SchemaPathRules.Supported, TPathKind>) & (TModel extends AbstractControl ? unknown : TModel extends ReadonlyArray<any> ? unknown : TModel extends Record<string, any> ? {
517517
[K in keyof TModel]: MaybeSchemaPathTree<TModel[K], PathKind.Child>;
518518
} : unknown);
519519

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import {Signal, ɵFieldState} from '@angular/core';
1010
import {AbstractControl} from '@angular/forms';
1111
import type {Field} from './field_directive';
12-
import type {MetadataKey} from './rules/metadata';
13-
import type {ValidationError} from './rules/validation/validation_errors';
12+
import type {ValidationError, MetadataKey} from './rules';
1413

1514
/**
1615
* Symbol used to retain generic type information when it would otherwise be lost.
@@ -169,7 +168,7 @@ export type FieldTree<TModel, TKey extends string | number = string | number> =
169168
// Children:
170169
([TModel] extends [AbstractControl]
171170
? object
172-
: [TModel] extends [Array<infer U>]
171+
: [TModel] extends [ReadonlyArray<infer U>]
173172
? ReadonlyArrayLike<MaybeFieldTree<U, number>>
174173
: TModel extends Record<string, any>
175174
? Subfields<TModel>
@@ -408,7 +407,7 @@ export type SchemaPathTree<TModel, TPathKind extends PathKind = PathKind.Root> =
408407
(TModel extends AbstractControl
409408
? unknown
410409
: // Array paths have no subpaths
411-
TModel extends Array<any>
410+
TModel extends ReadonlyArray<any>
412411
? unknown
413412
: // Object subfields
414413
TModel extends Record<string, any>

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {signal, WritableSignal} from '@angular/core';
10-
import {form, required, schema, SchemaFn} from '../../public_api';
10+
import {FieldTree, form, required, schema, SchemaFn} from '../../public_api';
1111

1212
interface Order {
1313
id: string;
@@ -87,5 +87,30 @@ function typeVerificationOnlyDoNotRunMe() {
8787
type RecursiveType = (number | RecursiveType)[];
8888
form(signal<RecursiveType>([5]));
8989
});
90+
91+
it('should allow ReadonlyArray in model and be iterable', () => {
92+
interface Order {
93+
readonly products: readonly string[];
94+
}
95+
const order: WritableSignal<Order> = null!;
96+
const f = form(order);
97+
// Iterating over products should yield FieldTree<string> items, not [string, FieldTree] entries
98+
for (const product of f.products) {
99+
const p: FieldTree<string> = product;
100+
p().value();
101+
}
102+
});
103+
104+
it('should allow Array in model and be iterable', () => {
105+
interface Order {
106+
products: string[];
107+
}
108+
const order: WritableSignal<Order> = null!;
109+
const f = form(order);
110+
for (const product of f.products) {
111+
const p: FieldTree<string> = product;
112+
p().value();
113+
}
114+
});
90115
});
91116
}

0 commit comments

Comments
 (0)