@angular/forms
Version:
Angular - directives and services for creating forms
306 lines (294 loc) • 8.98 kB
JavaScript
/**
* @license Angular v21.0.5
* (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 } 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 Error(`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;
field;
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