UNPKG

@angular/forms

Version:

Angular - directives and services for creating forms

320 lines (308 loc) 9.15 kB
/** * @license Angular v21.0.6 * (c) 2010-2025 Google LLC. https://angular.dev/ * License: MIT */ import { FieldNode, getInjectorFromOptions, FieldNodeState, FieldNodeStructure, calculateValidationSelfStatus, BasicFieldAdapter, form, normalizeFormArgs } from './_structure-chunk.mjs'; import { linkedSignal, untracked, runInInjectionContext, computed, signal, ɵRuntimeError as _RuntimeError } from '@angular/core'; import { FormGroup, FormArray, AbstractControl } from '@angular/forms'; import { toSignal } from '@angular/core/rxjs-interop'; import { ReplaySubject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import '@angular/core/primitives/signals'; class CompatFieldNode extends FieldNode { options; control; constructor(options) { super(options); this.options = options; this.control = this.options.control; } } function makeCreateDestroySubject() { let destroy$ = new ReplaySubject(1); return () => { if (destroy$) { destroy$.next(); destroy$.complete(); } return destroy$ = new ReplaySubject(1); }; } function extractControlPropToSignal(options, makeSignal) { const injector = getInjectorFromOptions(options); const createDestroySubject = makeCreateDestroySubject(); const signalOfControlSignal = linkedSignal({ ...(ngDevMode ? { debugName: "signalOfControlSignal" } : {}), source: options.control, computation: control => { return untracked(() => { return runInInjectionContext(injector, () => makeSignal(control, createDestroySubject())); }); } }); return computed(() => signalOfControlSignal()()); } const getControlStatusSignal = (options, getValue) => { return extractControlPropToSignal(options, (c, destroy$) => toSignal(c.statusChanges.pipe(map(() => getValue(c)), takeUntil(destroy$)), { initialValue: getValue(c) })); }; const getControlEventsSignal = (options, getValue) => { return extractControlPropToSignal(options, (c, destroy$) => toSignal(c.events.pipe(map(() => { return getValue(c); }), takeUntil(destroy$)), { initialValue: getValue(c) })); }; class CompatNodeState extends FieldNodeState { compatNode; touched; dirty; disabled; control; constructor(compatNode, options) { super(compatNode); this.compatNode = compatNode; this.control = options.control; this.touched = getControlEventsSignal(options, c => c.touched); this.dirty = getControlEventsSignal(options, c => c.dirty); const controlDisabled = getControlStatusSignal(options, c => c.disabled); this.disabled = computed(() => { return controlDisabled() || this.disabledReasons().length > 0; }, ...(ngDevMode ? [{ debugName: "disabled" }] : [])); } markAsDirty() { this.control().markAsDirty(); } markAsTouched() { this.control().markAsTouched(); } markAsPristine() { this.control().markAsPristine(); } markAsUntouched() { this.control().markAsUntouched(); } } function getParentFromOptions(options) { if (options.kind === 'root') { return undefined; } return options.parent; } function getFieldManagerFromOptions(options) { if (options.kind === 'root') { return options.fieldManager; } return options.parent.structure.root.structure.fieldManager; } function getControlValueSignal(options) { const value = extractControlPropToSignal(options, (control, destroy$) => { return toSignal(control.valueChanges.pipe(map(() => control.getRawValue()), takeUntil(destroy$)), { initialValue: control.getRawValue() }); }); value.set = value => { options.control().setValue(value); }; value.update = fn => { value.set(fn(value())); }; return value; } class CompatStructure extends FieldNodeStructure { value; keyInParent; root; pathKeys; children = signal([], ...(ngDevMode ? [{ debugName: "children" }] : [])); childrenMap = computed(() => undefined, ...(ngDevMode ? [{ debugName: "childrenMap" }] : [])); parent; fieldManager; constructor(node, options) { super(options.logic, node, () => { throw new _RuntimeError(1911, ngDevMode && `Compat nodes don't have children.`); }); this.value = getControlValueSignal(options); this.parent = getParentFromOptions(options); this.root = this.parent?.structure.root ?? node; this.fieldManager = getFieldManagerFromOptions(options); const identityInParent = options.kind === 'child' ? options.identityInParent : undefined; const initialKeyInParent = options.kind === 'child' ? options.initialKeyInParent : undefined; this.keyInParent = this.createKeyInParent(options, identityInParent, initialKeyInParent); this.pathKeys = computed(() => this.parent ? [...this.parent.structure.pathKeys(), this.keyInParent()] : [], ...(ngDevMode ? [{ debugName: "pathKeys" }] : [])); } getChild() { return undefined; } } class CompatValidationError { kind = 'compat'; control; fieldTree; context; message; constructor({ context, kind, control }) { this.context = context; this.kind = kind; this.control = control; } } function reactiveErrorsToSignalErrors(errors, control) { if (errors === null) { return []; } return Object.entries(errors).map(([kind, context]) => { return new CompatValidationError({ context, kind, control }); }); } function extractNestedReactiveErrors(control) { const errors = []; if (control.errors) { errors.push(...reactiveErrorsToSignalErrors(control.errors, control)); } if (control instanceof FormGroup || control instanceof FormArray) { for (const c of Object.values(control.controls)) { errors.push(...extractNestedReactiveErrors(c)); } } return errors; } const EMPTY_ARRAY_SIGNAL = computed(() => [], ...(ngDevMode ? [{ debugName: "EMPTY_ARRAY_SIGNAL" }] : [])); const TRUE_SIGNAL = computed(() => true, ...(ngDevMode ? [{ debugName: "TRUE_SIGNAL" }] : [])); class CompatValidationState { syncValid; errors; pending; invalid; valid; constructor(options) { this.syncValid = getControlStatusSignal(options, c => c.status === 'VALID'); this.errors = getControlStatusSignal(options, extractNestedReactiveErrors); this.pending = getControlStatusSignal(options, c => c.pending); this.valid = getControlStatusSignal(options, c => { return c.valid; }); this.invalid = getControlStatusSignal(options, c => { return c.invalid; }); } asyncErrors = EMPTY_ARRAY_SIGNAL; errorSummary = EMPTY_ARRAY_SIGNAL; rawSyncTreeErrors = EMPTY_ARRAY_SIGNAL; syncErrors = EMPTY_ARRAY_SIGNAL; rawAsyncErrors = EMPTY_ARRAY_SIGNAL; shouldSkipValidation = TRUE_SIGNAL; status = computed(() => { return calculateValidationSelfStatus(this); }, ...(ngDevMode ? [{ debugName: "status" }] : [])); } class CompatFieldAdapter { basicAdapter = new BasicFieldAdapter(); newRoot(fieldManager, value, pathNode, adapter) { if (value() instanceof AbstractControl) { return createCompatNode({ kind: 'root', fieldManager, value, pathNode, logic: pathNode.builder.build(), fieldAdapter: adapter }); } return this.basicAdapter.newRoot(fieldManager, value, pathNode, adapter); } createNodeState(node, options) { if (!options.control) { return this.basicAdapter.createNodeState(node); } return new CompatNodeState(node, options); } createStructure(node, options) { if (!options.control) { return this.basicAdapter.createStructure(node, options); } return new CompatStructure(node, options); } createValidationState(node, options) { if (!options.control) { return this.basicAdapter.createValidationState(node); } return new CompatValidationState(options); } newChild(options) { const value = options.parent.value()[options.initialKeyInParent]; if (value instanceof AbstractControl) { return createCompatNode(options); } return new FieldNode(options); } } function createCompatNode(options) { const control = options.kind === 'root' ? options.value : computed(() => { return options.parent.value()[options.initialKeyInParent]; }); return new CompatFieldNode({ ...options, control }); } function compatForm(...args) { const [model, maybeSchema, maybeOptions] = normalizeFormArgs(args); const options = { ...maybeOptions, adapter: new CompatFieldAdapter() }; const schema = maybeSchema || (() => {}); return form(model, schema, options); } const NG_STATUS_CLASSES = { 'ng-touched': ({ state }) => state().touched(), 'ng-untouched': ({ state }) => !state().touched(), 'ng-dirty': ({ state }) => state().dirty(), 'ng-pristine': ({ state }) => !state().dirty(), 'ng-valid': ({ state }) => state().valid(), 'ng-invalid': ({ state }) => state().invalid(), 'ng-pending': ({ state }) => state().pending() }; export { CompatValidationError, NG_STATUS_CLASSES, compatForm }; //# sourceMappingURL=signals-compat.mjs.map