diff --git a/adev/src/assets/BUILD.bazel b/adev/src/assets/BUILD.bazel index 4a641c7b4ce..c5754137afc 100644 --- a/adev/src/assets/BUILD.bazel +++ b/adev/src/assets/BUILD.bazel @@ -68,6 +68,7 @@ copy_to_directory( "//packages/elements:elements_docs", "//packages/forms:forms_docs", "//packages/forms/signals:forms_signals_docs", + "//packages/forms/signals/compat:forms_signals_compat_docs", "//packages/localize:localize_docs", "//packages/localize/src/localize:localize_init_docs", "//packages/platform-browser:platform-browser_docs", diff --git a/goldens/public-api/forms/signals/compat/index.api.md b/goldens/public-api/forms/signals/compat/index.api.md new file mode 100644 index 00000000000..03f45ae63e6 --- /dev/null +++ b/goldens/public-api/forms/signals/compat/index.api.md @@ -0,0 +1,57 @@ +## API Report File for "@angular/forms_signals_compat" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AbstractControl } from '@angular/forms'; +import * as _angular_forms from '@angular/forms'; +import { ControlValueAccessor } from '@angular/forms'; +import { DestroyableInjector } from '@angular/core'; +import { FormControlStatus } from '@angular/forms'; +import * as i0 from '@angular/core'; +import { Injector } from '@angular/core'; +import { NgControl } from '@angular/forms'; +import { Signal } from '@angular/core'; +import { ValidationErrors } from '@angular/forms'; +import { ValidatorFn } from '@angular/forms'; +import { WritableSignal } from '@angular/core'; +import { ɵCONTROL } from '@angular/core'; +import { ɵControl } from '@angular/core'; +import { ɵFieldState } from '@angular/core'; +import { ɵInteropControl } from '@angular/core'; + +// @public +export function compatForm(model: WritableSignal): FieldTree; + +// @public +export function compatForm(model: WritableSignal, schemaOrOptions: SchemaOrSchemaFn | CompatFormOptions): FieldTree; + +// @public +export function compatForm(model: WritableSignal, schema: SchemaOrSchemaFn, options: CompatFormOptions): FieldTree; + +// @public +export type CompatFormOptions = Omit; + +// @public +export class CompatValidationError implements ValidationError { + constructor({ context, kind, control }: { + context: T; + kind: string; + control: AbstractControl; + }); + // (undocumented) + readonly context: T; + // (undocumented) + readonly control: AbstractControl; + // (undocumented) + readonly field: FieldTree; + // (undocumented) + readonly kind: string; + // (undocumented) + readonly message?: string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 87099acb7a1..ea6a7a277db 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -108,6 +108,7 @@ filegroup( "//packages/elements:elements_docs_extraction", "//packages/forms:forms_docs_extraction", "//packages/forms/signals:forms_signals_docs_extraction", + "//packages/forms/signals/compat:forms_signals_compat_docs_extraction", "//packages/localize:localize_docs_extraction", "//packages/localize/src/localize:localize_init_docs_extraction", "//packages/platform-browser:platform-browser_docs_extraction", diff --git a/packages/forms/BUILD.bazel b/packages/forms/BUILD.bazel index ff222b7b2a1..1cb1907aeef 100644 --- a/packages/forms/BUILD.bazel +++ b/packages/forms/BUILD.bazel @@ -36,6 +36,7 @@ ng_package( deps = [ "//packages/forms", "//packages/forms/signals", + "//packages/forms/signals/compat", ], ) diff --git a/packages/forms/signals/compat/BUILD.bazel b/packages/forms/signals/compat/BUILD.bazel index 05f4d34c580..4940cecfa6d 100644 --- a/packages/forms/signals/compat/BUILD.bazel +++ b/packages/forms/signals/compat/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools:defaults.bzl", "ng_project") +load("//tools:defaults.bzl", "generate_api_docs", "ng_project") package(default_visibility = ["//visibility:public"]) @@ -18,3 +18,22 @@ ng_project( "//packages/forms/signals", ], ) + +filegroup( + name = "files_for_docgen", + srcs = glob([ + "*.ts", + "src/**/*.ts", + ]) + ["PACKAGE.md"], +) + +generate_api_docs( + name = "forms_signals_compat_docs", + srcs = [ + ":files_for_docgen", + "//packages:common_files_and_deps_for_docs", + "//packages/common:files_for_docgen", + ], + entry_point = ":index.ts", + module_name = "@angular/forms/signals/compat", +) diff --git a/packages/forms/signals/compat/PACKAGE.md b/packages/forms/signals/compat/PACKAGE.md new file mode 100644 index 00000000000..835a6a0931f --- /dev/null +++ b/packages/forms/signals/compat/PACKAGE.md @@ -0,0 +1,4 @@ +# APIs for Signal Forms interop with Reactive Forms + +This package contains APIs to enable interoperability between signal-based forms and reactive forms. +It allows developers to incrementally migrate their existing forms implementations to signal-based forms. diff --git a/packages/forms/signals/compat/index.ts b/packages/forms/signals/compat/index.ts new file mode 100644 index 00000000000..ffb1b7aab41 --- /dev/null +++ b/packages/forms/signals/compat/index.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +// This file is not used to build this module. It is only used during editing +// by the TypeScript language service and during build for verification. `ngc` +// replaces this file with production index.ts when it rewrites private symbol +// names. + +export * from './public_api'; diff --git a/packages/forms/signals/compat/public_api.ts b/packages/forms/signals/compat/public_api.ts new file mode 100644 index 00000000000..f13ef45a7d0 --- /dev/null +++ b/packages/forms/signals/compat/public_api.ts @@ -0,0 +1,15 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/** + * @module + * @description + * Entry point for all public APIs of this package. + */ +export * from './src/api/compat_form'; +export * from './src/api/compat_validation_error'; diff --git a/packages/forms/signals/compat/src/compat_form.ts b/packages/forms/signals/compat/src/api/compat_form.ts similarity index 91% rename from packages/forms/signals/compat/src/compat_form.ts rename to packages/forms/signals/compat/src/api/compat_form.ts index c3478901242..55d404546ca 100644 --- a/packages/forms/signals/compat/src/compat_form.ts +++ b/packages/forms/signals/compat/src/api/compat_form.ts @@ -6,12 +6,18 @@ * found in the LICENSE file at https://angular.dev/license */ -import {FieldTree, PathKind, SchemaOrSchemaFn} from '../../src/api/types'; -import {form, FormOptions} from '../../public_api'; -import {CompatFieldAdapter} from './compat_field_adapter'; -import {normalizeFormArgs} from '../../src/util/normalize_form_args'; import {WritableSignal} from '@angular/core'; +import {form, FormOptions} from '../../../public_api'; +import {FieldTree, PathKind, SchemaOrSchemaFn} from '../../../src/api/types'; +import {normalizeFormArgs} from '../../../src/util/normalize_form_args'; +import {CompatFieldAdapter} from '../compat_field_adapter'; +/** + * Options that may be specified when creating a compat form. + * + * @category interop + * @experimental 21.0.0 + */ export type CompatFormOptions = Omit; /** diff --git a/packages/forms/signals/compat/src/api/compat_validation_error.ts b/packages/forms/signals/compat/src/api/compat_validation_error.ts new file mode 100644 index 00000000000..135e981766c --- /dev/null +++ b/packages/forms/signals/compat/src/api/compat_validation_error.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {AbstractControl} from '@angular/forms'; +import {FieldTree} from '../../../src/api/types'; +import {ValidationError} from '../../../src/api/validation_errors'; + +/** + * An error used for compat errors. + * + * @experimental 21.0.0 + * @category interop + */ +export class CompatValidationError implements ValidationError { + readonly kind: string = 'compat'; + readonly control: AbstractControl; + readonly field!: FieldTree; + readonly context: T; + readonly message?: string; + + constructor({context, kind, control}: {context: T; kind: string; control: AbstractControl}) { + this.context = context; + this.kind = kind; + this.control = control; + } +} diff --git a/packages/forms/signals/compat/src/compat_field_adapter.ts b/packages/forms/signals/compat/src/compat_field_adapter.ts index a7a4e6ac659..0625fd63a44 100644 --- a/packages/forms/signals/compat/src/compat_field_adapter.ts +++ b/packages/forms/signals/compat/src/compat_field_adapter.ts @@ -6,23 +6,23 @@ * found in the LICENSE file at https://angular.dev/license */ +import {computed, Signal, WritableSignal} from '@angular/core'; import {AbstractControl} from '@angular/forms'; +import {BasicFieldAdapter, FieldAdapter} from '../../src/field/field_adapter'; +import {FormFieldManager} from '../../src/field/manager'; +import {FieldNode} from '../../src/field/node'; +import {FieldNodeState} from '../../src/field/state'; import { ChildFieldNodeOptions, FieldNodeOptions, FieldNodeStructure, } from '../../src/field/structure'; -import {computed, Signal, WritableSignal} from '@angular/core'; -import {CompatFieldNode} from './compat_field_node'; -import {FieldNode} from '../../src/field/node'; -import {CompatValidationState} from './compat_validation_state'; import {ValidationState} from '../../src/field/validation'; -import {CompatChildFieldNodeOptions, CompatStructure} from './compat_structure'; -import {CompatNodeState} from './compat_node_state'; -import {FieldNodeState} from '../../src/field/state'; -import {FieldAdapter, BasicFieldAdapter} from '../../src/field/field_adapter'; import {FieldPathNode} from '../../src/schema/path_node'; -import {FormFieldManager} from '../../src/field/manager'; +import {CompatFieldNode} from './compat_field_node'; +import {CompatNodeState} from './compat_node_state'; +import {CompatChildFieldNodeOptions, CompatStructure} from './compat_structure'; +import {CompatValidationState} from './compat_validation_state'; /** * This is a tree-shakable Field adapter that can create a compat node diff --git a/packages/forms/signals/compat/src/compat_field_node.ts b/packages/forms/signals/compat/src/compat_field_node.ts index f9ad525608d..3ef8031aa6f 100644 --- a/packages/forms/signals/compat/src/compat_field_node.ts +++ b/packages/forms/signals/compat/src/compat_field_node.ts @@ -7,13 +7,13 @@ */ import {computed, linkedSignal, runInInjectionContext, Signal, untracked} from '@angular/core'; +import {toSignal} from '@angular/core/rxjs-interop'; import {AbstractControl} from '@angular/forms'; +import {Observable, ReplaySubject} from 'rxjs'; +import {map, takeUntil} from 'rxjs/operators'; import {FieldNode} from '../../src/field/node'; import {getInjectorFromOptions} from '../../src/field/util'; -import {toSignal} from '@angular/core/rxjs-interop'; -import {map, takeUntil} from 'rxjs/operators'; import type {CompatFieldNodeOptions} from './compat_structure'; -import {Observable, ReplaySubject} from 'rxjs'; /** * Field node with additional control property. diff --git a/packages/forms/signals/compat/src/compat_node_state.ts b/packages/forms/signals/compat/src/compat_node_state.ts index 5a6b307e8fb..fcf2cebe644 100644 --- a/packages/forms/signals/compat/src/compat_node_state.ts +++ b/packages/forms/signals/compat/src/compat_node_state.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -import {FieldNodeState} from '../../src/field/state'; import {computed, Signal} from '@angular/core'; import {AbstractControl} from '@angular/forms'; +import {FieldNodeState} from '../../src/field/state'; import {CompatFieldNode, getControlEventsSignal, getControlStatusSignal} from './compat_field_node'; import {CompatFieldNodeOptions} from './compat_structure'; diff --git a/packages/forms/signals/compat/src/compat_validation_error.ts b/packages/forms/signals/compat/src/compat_validation_error.ts index ea58501fa02..5fdbaad5ec1 100644 --- a/packages/forms/signals/compat/src/compat_validation_error.ts +++ b/packages/forms/signals/compat/src/compat_validation_error.ts @@ -7,8 +7,7 @@ */ import {AbstractControl, FormArray, FormGroup, ValidationErrors} from '@angular/forms'; -import {ValidationError} from '../../src/api/validation_errors'; -import {FieldTree} from '../../src/api/types'; +import {CompatValidationError} from './api/compat_validation_error'; /** * Converts reactive form validation error to signal forms CompatValidationError. @@ -44,21 +43,3 @@ export function extractNestedReactiveErrors(control: AbstractControl): CompatVal return errors; } - -/** - * An error used for compat errors. - */ -export class CompatValidationError implements ValidationError { - readonly kind: string = 'compat'; - readonly control: AbstractControl; - readonly field!: FieldTree; - context: T; - - constructor({context, kind, control}: {context: T; kind: string; control: AbstractControl}) { - this.context = context; - this.kind = kind; - this.control = control; - } - - message?: string | undefined; -} diff --git a/packages/forms/signals/compat/src/compat_validation_state.ts b/packages/forms/signals/compat/src/compat_validation_state.ts index 0dc634d22b3..8052ed38991 100644 --- a/packages/forms/signals/compat/src/compat_validation_state.ts +++ b/packages/forms/signals/compat/src/compat_validation_state.ts @@ -6,13 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, signal, Signal} from '@angular/core'; +import {computed, Signal} from '@angular/core'; import {AbstractControl} from '@angular/forms'; +import {ValidationError} from '../../src/api/validation_errors'; +import {calculateValidationSelfStatus, ValidationState} from '../../src/field/validation'; +import type {CompatValidationError} from './api/compat_validation_error'; import {getControlStatusSignal} from './compat_field_node'; import {CompatFieldNodeOptions} from './compat_structure'; -import {calculateValidationSelfStatus, ValidationState} from '../../src/field/validation'; -import {CompatValidationError, extractNestedReactiveErrors} from './compat_validation_error'; -import {ValidationError} from '../../src/api/validation_errors'; +import {extractNestedReactiveErrors} from './compat_validation_error'; // Readonly signal containing an empty array, used for optimization. const EMPTY_ARRAY_SIGNAL = computed(() => []); diff --git a/packages/forms/signals/test/node/compat/compat.spec.ts b/packages/forms/signals/test/node/compat/compat.spec.ts index 625823069d3..1f2f0f7ca73 100644 --- a/packages/forms/signals/test/node/compat/compat.spec.ts +++ b/packages/forms/signals/test/node/compat/compat.spec.ts @@ -7,6 +7,10 @@ */ import {ApplicationRef, Injector, signal} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; +import {compatForm} from '../../../compat/src/api/compat_form'; +import {CompatValidationError} from '../../../compat/src/api/compat_validation_error'; import { customError, disabled, @@ -23,10 +27,6 @@ import { validate, validateTree, } from '../../../public_api'; -import {TestBed} from '@angular/core/testing'; -import {FormControl, FormGroup, Validators} from '@angular/forms'; -import {CompatValidationError} from '../../../compat/src/compat_validation_error'; -import {compatForm} from '../../../compat/src/compat_form'; function promiseWithResolvers(): { promise: Promise; diff --git a/packages/forms/signals/test/node/compat/compat_validation_error.spec.ts b/packages/forms/signals/test/node/compat/compat_validation_error.spec.ts index 7a8447840cc..6e9d2c6e92f 100644 --- a/packages/forms/signals/test/node/compat/compat_validation_error.spec.ts +++ b/packages/forms/signals/test/node/compat/compat_validation_error.spec.ts @@ -7,10 +7,10 @@ */ import {FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn} from '@angular/forms'; +import {CompatValidationError} from '../../../compat/src/api/compat_validation_error'; import { - reactiveErrorsToSignalErrors, - CompatValidationError, extractNestedReactiveErrors, + reactiveErrorsToSignalErrors, } from '../../../compat/src/compat_validation_error'; describe('destroy$', () => {