diff --git a/adev-es/src/app/routing/sub-navigation-data.ts b/adev-es/src/app/routing/sub-navigation-data.ts
index 9fe4a39..6b79cde 100644
--- a/adev-es/src/app/routing/sub-navigation-data.ts
+++ b/adev-es/src/app/routing/sub-navigation-data.ts
@@ -434,32 +434,32 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
status: 'new',
children: [
{
- label: 'Overview',
+ label: 'Visión general',
path: 'guide/forms/signals/overview',
contentPath: 'guide/forms/signals/overview',
},
{
- label: 'Form models',
+ label: 'Modelos de formularios',
path: 'guide/forms/signals/models',
contentPath: 'guide/forms/signals/models',
},
{
- label: 'Field state management',
+ label: 'Gestión de estado de campos',
path: 'guide/forms/signals/field-state-management',
contentPath: 'guide/forms/signals/field-state-management',
},
{
- label: 'Validation',
+ label: 'Validación',
path: 'guide/forms/signals/validation',
contentPath: 'guide/forms/signals/validation',
},
{
- label: 'Custom controls',
+ label: 'Controles personalizados',
path: 'guide/forms/signals/custom-controls',
contentPath: 'guide/forms/signals/custom-controls',
},
{
- label: 'Comparison with other form systems',
+ label: 'Comparación con otros enfoques de formularios',
path: 'guide/forms/signals/comparison',
contentPath: 'guide/forms/signals/comparison',
},
diff --git a/adev-es/src/content/guide/forms/signals/comparison.en.md b/adev-es/src/content/guide/forms/signals/comparison.en.md
new file mode 100644
index 0000000..d2690a9
--- /dev/null
+++ b/adev-es/src/content/guide/forms/signals/comparison.en.md
@@ -0,0 +1,163 @@
+# Comparison with other form approaches
+
+Angular provides three approaches to building forms: Signal Forms, Reactive Forms, and Template-driven Forms. Each has distinct patterns for managing state, validation, and data flow. This guide helps you understand the differences and choose the right approach for your project.
+
+NOTE: Signal Forms are [experimental](reference/releases#experimental) as of Angular v21. The API may change before stabilizing.
+
+## Quick comparison
+
+| Feature | Signal Forms | Reactive Forms | Template-driven Forms |
+| ---------------- | ---------------------------------- | ------------------------------------- | ----------------------- |
+| Source of truth | User-defined writable signal model | `FormControl`/`FormGroup` | User model in component |
+| Type safety | Inferred from model | Explicit with typed forms | Minimal |
+| Validation | Schema with path-based validators | List of validators passed to Controls | Directive-based |
+| State management | Signal-based | Observable-based | Angular-managed |
+| Setup | Signal + schema function | FormControl tree | NgModel in template |
+| Best for | Signal-based apps | Complex forms | Simple forms |
+| Learning curve | Medium | Medium-High | Low |
+| Status | Experimental (v21+) | Stable | Stable |
+
+## By example: Login form
+
+The best way to understand the differences is to see the same form implemented in all three approaches.
+
+
+
+
+
+
+
+## Understanding the differences
+
+The three approaches make different design choices that affect how you write and maintain your forms. These differences stem from where each approach stores form state and how it manages validation.
+
+### Where your form data lives
+
+The most fundamental difference is where each approach considers the "source of truth" for form values.
+
+Signal Forms stores data in a writable signal. When you need the current form values, you call the signal:
+
+```ts
+const credentials = this.loginModel(); // { email: '...', password: '...' }
+```
+
+This keeps your form data in a single reactive container that automatically notifies Angular when values change. The form structure mirrors your data model exactly.
+
+Reactive Forms stores data inside FormControl and FormGroup instances. You access values through the form hierarchy:
+
+```ts
+const credentials = this.loginForm.value; // { email: '...', password: '...' }
+```
+
+This separates form state management from your component's data model. The form structure is explicit but requires more setup code.
+
+Template-driven Forms stores data in component properties. You access values directly:
+
+```ts
+const credentials = { email: this.email, password: this.password };
+```
+
+This is the most direct approach but requires manually assembling values when you need them. Angular manages form state through directives in the template.
+
+### How validation works
+
+Each approach defines validation rules differently, affecting where your validation logic lives and how you maintain it.
+
+Signal Forms uses a schema function where you bind validators to field paths:
+
+```ts
+loginForm = form(this.loginModel, (fieldPath) => {
+ required(fieldPath.email, { message: 'Email is required' });
+ email(fieldPath.email, { message: 'Enter a valid email address' });
+});
+```
+
+All validation rules live together in one place. The schema function runs once during form creation, and validators execute automatically when field values change. Error messages are part of the validation definition.
+
+Reactive Forms attaches validators when creating controls:
+
+```ts
+loginForm = new FormGroup({
+ email: new FormControl('', [Validators.required, Validators.email])
+});
+```
+
+Validators are tied to individual controls in the form structure. This distributes validation across your form definition. Error messages typically live in your template.
+
+Template-driven Forms uses directive attributes in the template:
+
+```html
+
+```
+
+Validation rules live in your template alongside the HTML. This keeps validation close to the UI but spreads logic across template and component.
+
+### Type safety and autocomplete
+
+TypeScript integration differs significantly between approaches, affecting how much the compiler helps you avoid errors.
+
+Signal Forms infers types from your model structure:
+
+```ts
+const loginModel = signal({ email: '', password: '' });
+const loginForm = form(loginModel);
+// TypeScript knows: loginForm.email exists and returns FieldState
+```
+
+You define your data shape once in the signal, and TypeScript automatically knows what fields exist and their types. Accessing `loginForm.username` (which doesn't exist) produces a type error.
+
+Reactive Forms requires explicit type annotations with typed forms:
+
+```ts
+const loginForm = new FormGroup({
+ email: new FormControl(''),
+ password: new FormControl('')
+});
+// TypeScript knows: loginForm.controls.email is FormControl
+```
+
+You specify types for each control individually. TypeScript validates your form structure, but you maintain type information separately from your data model.
+
+Template-driven Forms offers minimal type safety:
+
+```ts
+email = '';
+password = '';
+// TypeScript only knows these are strings, no form-level typing
+```
+
+TypeScript understands your component properties but has no knowledge of form structure or validation. You lose compile-time checking for form operations.
+
+## Choose your approach
+
+### Use Signal Forms if:
+
+- You're building new signal-based applications (Angular v21+)
+- You want type safety inferred from your model structure
+- You're comfortable working with experimental features
+- Schema-based validation appeals to you
+- Your team is familiar with signals
+
+### Use Reactive Forms if:
+
+- You need production-ready stability
+- You're building complex, dynamic forms
+- You prefer observable-based patterns
+- You need fine-grained control over form state
+- You're working on an existing reactive forms codebase
+
+### Use Template-driven Forms if:
+
+- You're building simple forms (login, contact, search)
+- You're doing rapid prototyping
+- Your form logic is straightforward
+- You prefer keeping form logic in templates
+- You're working on an existing template-driven codebase
+
+## Next steps
+
+To learn more about each approach:
+
+- **Signal Forms**: See the [Overview guide](guide/forms/signals/overview) to get started, or dive into [Form Models](guide/forms/signals/models), [Validation](guide/forms/signals/validation), and [Field State Management](guide/forms/signals/field-state-management)
+- **Reactive Forms**: See the [Reactive Forms guide](guide/forms/reactive-forms) in Angular documentation
+- **Template-driven Forms**: See the [Template-driven Forms guide](guide/forms/template-driven-forms) in Angular documentation
diff --git a/adev-es/src/content/guide/forms/signals/comparison.md b/adev-es/src/content/guide/forms/signals/comparison.md
index d2690a9..3d3c1da 100644
--- a/adev-es/src/content/guide/forms/signals/comparison.md
+++ b/adev-es/src/content/guide/forms/signals/comparison.md
@@ -1,25 +1,25 @@
-# Comparison with other form approaches
+# Comparación con otros enfoques de formularios
-Angular provides three approaches to building forms: Signal Forms, Reactive Forms, and Template-driven Forms. Each has distinct patterns for managing state, validation, and data flow. This guide helps you understand the differences and choose the right approach for your project.
+Angular proporciona tres enfoques para construir formularios: Signal Forms, Reactive Forms, y Template-driven Forms. Cada uno tiene patrones distintos para gestionar estado, validación, y flujo de datos. Esta guía te ayuda a entender las diferencias y elegir el enfoque correcto para tu proyecto.
-NOTE: Signal Forms are [experimental](reference/releases#experimental) as of Angular v21. The API may change before stabilizing.
+NOTA: Signal Forms son [experimentales](reference/releases#experimental) a partir de Angular v21. La API puede cambiar antes de estabilizarse.
-## Quick comparison
+## Comparación rápida
-| Feature | Signal Forms | Reactive Forms | Template-driven Forms |
-| ---------------- | ---------------------------------- | ------------------------------------- | ----------------------- |
-| Source of truth | User-defined writable signal model | `FormControl`/`FormGroup` | User model in component |
-| Type safety | Inferred from model | Explicit with typed forms | Minimal |
-| Validation | Schema with path-based validators | List of validators passed to Controls | Directive-based |
-| State management | Signal-based | Observable-based | Angular-managed |
-| Setup | Signal + schema function | FormControl tree | NgModel in template |
-| Best for | Signal-based apps | Complex forms | Simple forms |
-| Learning curve | Medium | Medium-High | Low |
-| Status | Experimental (v21+) | Stable | Stable |
+| Característica | Signal Forms | Reactive Forms | Template-driven Forms |
+| ----------------------- | ---------------------------------- | ------------------------------------- | ----------------------- |
+| Fuente de verdad | Signal editable definido por usuario | `FormControl`/`FormGroup` | Modelo de usuario en componente |
+| Seguridad de tipos | Inferida del modelo | Explícita con formularios tipados | Mínima |
+| Validación | Esquema con validadores basados en ruta | Lista de validadores pasados a Controls | Basada en directivas |
+| Gestión de estado | Basada en signals | Basada en observables | Gestionada por Angular |
+| Configuración | Signal + función de esquema | Árbol de FormControl | NgModel en plantilla |
+| Mejor para | Aplicaciones basadas en signals | Formularios complejos | Formularios simples |
+| Curva de aprendizaje | Media | Media-Alta | Baja |
+| Estado | Experimental (v21+) | Estable | Estable |
-## By example: Login form
+## Por ejemplo: Formulario de inicio de sesión
-The best way to understand the differences is to see the same form implemented in all three approaches.
+La mejor manera de entender las diferencias es ver el mismo formulario implementado en los tres enfoques.
@@ -27,43 +27,43 @@ The best way to understand the differences is to see the same form implemented i
-## Understanding the differences
+## Entendiendo las diferencias
-The three approaches make different design choices that affect how you write and maintain your forms. These differences stem from where each approach stores form state and how it manages validation.
+Los tres enfoques toman decisiones de diseño diferentes que afectan cómo escribes y mantienes tus formularios. Estas diferencias provienen de dónde cada enfoque almacena el estado del formulario y cómo gestiona la validación.
-### Where your form data lives
+### Dónde viven los datos de tu formulario
-The most fundamental difference is where each approach considers the "source of truth" for form values.
+La diferencia más fundamental es dónde cada enfoque considera la "fuente de verdad" para los valores del formulario.
-Signal Forms stores data in a writable signal. When you need the current form values, you call the signal:
+Signal Forms almacena datos en un signal editable. Cuando necesitas los valores actuales del formulario, llamas al signal:
```ts
const credentials = this.loginModel(); // { email: '...', password: '...' }
```
-This keeps your form data in a single reactive container that automatically notifies Angular when values change. The form structure mirrors your data model exactly.
+Esto mantiene los datos de tu formulario en un contenedor reactivo único que notifica automáticamente a Angular cuando los valores cambian. La estructura del formulario refleja exactamente tu modelo de datos.
-Reactive Forms stores data inside FormControl and FormGroup instances. You access values through the form hierarchy:
+Reactive Forms almacena datos dentro de instancias de FormControl y FormGroup. Accedes a los valores a través de la jerarquía del formulario:
```ts
const credentials = this.loginForm.value; // { email: '...', password: '...' }
```
-This separates form state management from your component's data model. The form structure is explicit but requires more setup code.
+Esto separa la gestión del estado del formulario del modelo de datos de tu componente. La estructura del formulario es explícita pero requiere más código de configuración.
-Template-driven Forms stores data in component properties. You access values directly:
+Template-driven Forms almacena datos en propiedades del componente. Accedes a los valores directamente:
```ts
const credentials = { email: this.email, password: this.password };
```
-This is the most direct approach but requires manually assembling values when you need them. Angular manages form state through directives in the template.
+Este es el enfoque más directo pero requiere ensamblar manualmente los valores cuando los necesitas. Angular gestiona el estado del formulario a través de directivas en la plantilla.
-### How validation works
+### Cómo funciona la validación
-Each approach defines validation rules differently, affecting where your validation logic lives and how you maintain it.
+Cada enfoque define reglas de validación de manera diferente, afectando dónde vive tu lógica de validación y cómo la mantienes.
-Signal Forms uses a schema function where you bind validators to field paths:
+Signal Forms usa una función de esquema donde vinculas validadores a rutas de campo:
```ts
loginForm = form(this.loginModel, (fieldPath) => {
@@ -72,9 +72,9 @@ loginForm = form(this.loginModel, (fieldPath) => {
});
```
-All validation rules live together in one place. The schema function runs once during form creation, and validators execute automatically when field values change. Error messages are part of the validation definition.
+Todas las reglas de validación viven juntas en un solo lugar. La función de esquema se ejecuta una vez durante la creación del formulario, y los validadores se ejecutan automáticamente cuando los valores de los campos cambian. Los mensajes de error son parte de la definición de validación.
-Reactive Forms attaches validators when creating controls:
+Reactive Forms adjunta validadores al crear controles:
```ts
loginForm = new FormGroup({
@@ -82,21 +82,21 @@ loginForm = new FormGroup({
});
```
-Validators are tied to individual controls in the form structure. This distributes validation across your form definition. Error messages typically live in your template.
+Los validadores están vinculados a controles individuales en la estructura del formulario. Esto distribuye la validación a través de tu definición de formulario. Los mensajes de error típicamente viven en tu plantilla.
-Template-driven Forms uses directive attributes in the template:
+Template-driven Forms usa atributos de directiva en la plantilla:
```html
```
-Validation rules live in your template alongside the HTML. This keeps validation close to the UI but spreads logic across template and component.
+Las reglas de validación viven en tu plantilla junto al HTML. Esto mantiene la validación cerca de la UI pero dispersa la lógica entre plantilla y componente.
-### Type safety and autocomplete
+### Seguridad de tipos y autocompletado
-TypeScript integration differs significantly between approaches, affecting how much the compiler helps you avoid errors.
+La integración de TypeScript difiere significativamente entre enfoques, afectando cuánto el compilador te ayuda a evitar errores.
-Signal Forms infers types from your model structure:
+Signal Forms infiere tipos de la estructura de tu modelo:
```ts
const loginModel = signal({ email: '', password: '' });
@@ -104,9 +104,9 @@ const loginForm = form(loginModel);
// TypeScript knows: loginForm.email exists and returns FieldState
```
-You define your data shape once in the signal, and TypeScript automatically knows what fields exist and their types. Accessing `loginForm.username` (which doesn't exist) produces a type error.
+Defines la forma de tus datos una vez en el signal, y TypeScript automáticamente sabe qué campos existen y sus tipos. Acceder a `loginForm.username` (que no existe) produce un error de tipo.
-Reactive Forms requires explicit type annotations with typed forms:
+Reactive Forms requiere anotaciones de tipo explícitas con formularios tipados:
```ts
const loginForm = new FormGroup({
@@ -116,9 +116,9 @@ const loginForm = new FormGroup({
// TypeScript knows: loginForm.controls.email is FormControl
```
-You specify types for each control individually. TypeScript validates your form structure, but you maintain type information separately from your data model.
+Especificas tipos para cada control individualmente. TypeScript valida tu estructura de formulario, pero mantienes la información de tipo separada de tu modelo de datos.
-Template-driven Forms offers minimal type safety:
+Template-driven Forms ofrece seguridad de tipos mínima:
```ts
email = '';
@@ -126,38 +126,38 @@ password = '';
// TypeScript only knows these are strings, no form-level typing
```
-TypeScript understands your component properties but has no knowledge of form structure or validation. You lose compile-time checking for form operations.
+TypeScript entiende las propiedades de tu componente pero no tiene conocimiento de la estructura del formulario o validación. Pierdes verificación en tiempo de compilación para operaciones de formulario.
-## Choose your approach
+## Elige tu enfoque
-### Use Signal Forms if:
+### Usa Signal Forms si:
-- You're building new signal-based applications (Angular v21+)
-- You want type safety inferred from your model structure
-- You're comfortable working with experimental features
-- Schema-based validation appeals to you
-- Your team is familiar with signals
+- Estás construyendo nuevas aplicaciones basadas en signals (Angular v21+)
+- Quieres seguridad de tipos inferida de la estructura de tu modelo
+- Te sientes cómodo trabajando con características experimentales
+- La validación basada en esquemas te atrae
+- Tu equipo está familiarizado con signals
-### Use Reactive Forms if:
+### Usa Reactive Forms si:
-- You need production-ready stability
-- You're building complex, dynamic forms
-- You prefer observable-based patterns
-- You need fine-grained control over form state
-- You're working on an existing reactive forms codebase
+- Necesitas estabilidad lista para producción
+- Estás construyendo formularios complejos y dinámicos
+- Prefieres patrones basados en observables
+- Necesitas control detallado sobre el estado del formulario
+- Estás trabajando en una base de código existente de reactive forms
-### Use Template-driven Forms if:
+### Usa Template-driven Forms si:
-- You're building simple forms (login, contact, search)
-- You're doing rapid prototyping
-- Your form logic is straightforward
-- You prefer keeping form logic in templates
-- You're working on an existing template-driven codebase
+- Estás construyendo formularios simples (login, contacto, búsqueda)
+- Estás haciendo prototipado rápido
+- Tu lógica de formulario es sencilla
+- Prefieres mantener la lógica del formulario en plantillas
+- Estás trabajando en una base de código existente de template-driven
-## Next steps
+## Próximos pasos
-To learn more about each approach:
+Para aprender más sobre cada enfoque:
-- **Signal Forms**: See the [Overview guide](guide/forms/signals/overview) to get started, or dive into [Form Models](guide/forms/signals/models), [Validation](guide/forms/signals/validation), and [Field State Management](guide/forms/signals/field-state-management)
-- **Reactive Forms**: See the [Reactive Forms guide](guide/forms/reactive-forms) in Angular documentation
-- **Template-driven Forms**: See the [Template-driven Forms guide](guide/forms/template-driven-forms) in Angular documentation
+- **Signal Forms**: Consulta la [guía de Visión general](guide/forms/signals/overview) para comenzar, o profundiza en [Modelos de Formulario](guide/forms/signals/models), [Validación](guide/forms/signals/validation), y [Gestión de Estado de Campo](guide/forms/signals/field-state-management)
+- **Reactive Forms**: Consulta la [guía de Reactive Forms](guide/forms/reactive-forms) en la documentación de Angular
+- **Template-driven Forms**: Consulta la [guía de Template-driven Forms](guide/forms/template-driven-forms) en la documentación de Angular
diff --git a/adev-es/src/content/guide/forms/signals/custom-controls.en.md b/adev-es/src/content/guide/forms/signals/custom-controls.en.md
new file mode 100644
index 0000000..1ce7026
--- /dev/null
+++ b/adev-es/src/content/guide/forms/signals/custom-controls.en.md
@@ -0,0 +1,428 @@
+# Custom Controls
+
+NOTE: This guide assumes familiarity with [Signal Forms essentials](essentials/signal-forms).
+
+The browser's built-in form controls (like input, select, textarea) handle common cases, but applications often need specialized inputs. A date picker with calendar UI, a rich text editor with formatting toolbar, or a tag selector with autocomplete all require custom implementations.
+
+Signal Forms works with any component that implements specific interfaces. A **control interface** defines the properties and signals that allow your component to communicate with the form system. When your component implements one of these interfaces, the `[field]` directive automatically connects your control to form state, validation, and data binding.
+
+## Creating a basic custom control
+
+Let's start with a minimal implementation and add features as needed.
+
+### Minimal input control
+
+A basic custom input only needs to implement the `FormValueControl` interface and define the required `value` model signal.
+
+```angular-ts
+import { Component, model } from '@angular/core';
+import { FormValueControl } from '@angular/forms/signals';
+
+@Component({
+ selector: 'app-basic-input',
+ template: `
+
+
+
+ `,
+})
+export class BasicInput implements FormValueControl {
+ /** The current input value */
+ value = model('');
+}
+```
+
+### Minimal checkbox control
+
+A checkbox-style control needs two things:
+
+1. Implement the `FormCheckboxControl` interface so the `Field` directive will recognize it as a form control
+2. Provide a `checked` model signal
+
+```angular-ts
+import { Component, model, ChangeDetectionStrategy } from '@angular/core';
+import { FormCheckboxControl } from '@angular/forms/signals';
+
+@Component({
+ selector: 'app-basic-toggle',
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class BasicToggle implements FormCheckboxControl {
+ /** Whether the toggle is checked */
+ checked = model(false);
+
+ toggle() {
+ this.checked.update(val => !val);
+ }
+}
+```
+
+### Using your custom control
+
+Once you've created a control, you can use it anywhere you would use a built-in input by adding the `Field` directive to it:
+
+```angular-ts
+import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
+import { form, Field, required } from '@angular/forms/signals';
+import { BasicInput } from './basic-input';
+import { BasicToggle } from './basic-toggle';
+
+@Component({
+ imports: [Field, BasicInput, BasicToggle],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class Registration {
+ registrationModel = signal({
+ email: '',
+ acceptTerms: false
+ });
+
+ registrationForm = form(this.registrationModel, (schemaPath) => {
+ required(schemaPath.email, { message: 'Email is required' });
+ required(schemaPath.acceptTerms, { message: 'You must accept the terms' });
+ });
+}
+```
+
+NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+
+The `[field]` directive works identically for custom controls and built-in inputs. Signal Forms treats them the same - validation runs, state updates, and data binding works automatically.
+
+## Understanding control interfaces
+
+Now that you've seen custom controls in action, let's explore how they integrate with Signal Forms.
+
+### Control interfaces
+
+The `BasicInput` and `BasicToggle` components you created implement specific control interfaces that tell Signal Forms how to interact with them.
+
+#### FormValueControl
+
+`FormValueControl` is the interface for most input types - text inputs, number inputs, date pickers, select dropdowns, and any control that edits a single value. When your component implements this interface:
+
+- **Required property**: Your component must provide a `value` model signal
+- **What the Field directive does**: Binds the form field's value to your control's `value` signal
+
+IMPORTANT: Controls implementing `FormValueControl` must NOT have a `checked` property
+
+#### FormCheckboxControl
+
+`FormCheckboxControl` is the interface for checkbox-like controls - toggles, switches, and any control that represents a boolean on/off state. When your component implements this interface:
+
+- **Required property**: Your component must provide a `checked` model signal
+- **What the Field directive does**: Binds the form field's value to your control's `checked` signal
+
+IMPORTANT: Controls implementing `FormCheckboxControl` must NOT have a `value` property
+
+### Optional state properties
+
+Both `FormValueControl` and `FormCheckboxControl` extend `FormUiControl` - a base interface that provides optional properties for integrating with form state.
+
+All properties are optional. Implement only what your control needs.
+
+#### Interaction state
+
+Track when users interact with your control:
+
+| Property | Purpose |
+| --------- | ------------------------------------------------ |
+| `touched` | Whether the user has interacted with the field |
+| `dirty` | Whether the value differs from its initial state |
+
+#### Validation state
+
+Display validation feedback to users:
+
+| Property | Purpose |
+| --------- | --------------------------------------- |
+| `errors` | Array of current validation errors |
+| `valid` | Whether the field is valid |
+| `invalid` | Whether the field has validation errors |
+| `pending` | Whether async validation is in progress |
+
+#### Availability state
+
+Control whether users can interact with your field:
+
+| Property | Purpose |
+| ----------------- | -------------------------------------------------------- |
+| `disabled` | Whether the field is disabled |
+| `disabledReasons` | Reasons why the field is disabled |
+| `readonly` | Whether the field is readonly (visible but not editable) |
+| `hidden` | Whether the field is hidden from view |
+
+NOTE: `disabledReasons` is an array of `DisabledReason` objects. Each object has a `field` property (reference to the field tree) and an optional `message` property. Access the message via `reason.message`.
+
+#### Validation constraints
+
+Receive validation constraint values from the form:
+
+| Property | Purpose |
+| ----------- | ---------------------------------------------------- |
+| `required` | Whether the field is required |
+| `min` | Minimum numeric value (`undefined` if no constraint) |
+| `max` | Maximum numeric value (`undefined` if no constraint) |
+| `minLength` | Minimum string length (undefined if no constraint) |
+| `maxLength` | Maximum string length (undefined if no constraint) |
+| `pattern` | Array of regular expression patterns to match |
+
+#### Field metadata
+
+| Property | Purpose |
+| -------- | ------------------------------------------------------------------ |
+| `name` | The field's name attribute (which is unique across forms and apps) |
+
+The "[Adding state signals](#adding-state-signals)" section below shows how to implement these properties in your controls.
+
+### How the Field directive works
+
+The `[field]` directive detects which interface your control implements and automatically binds the appropriate signals:
+
+```angular-ts
+import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
+import { form, Field, required } from '@angular/forms/signals';
+import { CustomInput } from './custom-input';
+import { CustomToggle } from './custom-toggle';
+
+@Component({
+ selector: 'app-my-form',
+ imports: [Field, CustomInput, CustomToggle],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class MyForm {
+ formModel = signal({
+ username: '',
+ subscribe: false
+ });
+
+ userForm = form(this.formModel, (schemaPath) => {
+ required(schemaPath.username, { message: 'Username is required' });
+ });
+}
+```
+
+TIP: For complete coverage of creating and managing form models, see the [Form Models guide](guide/forms/signals/models).
+
+When you bind `[field]="userForm.username"`, the Field directive:
+
+1. Detects your control implements `FormValueControl`
+2. Internally accesses `userForm.username().value()` and binds it to your control's `value` model signal
+3. Binds form state signals (`disabled()`, `errors()`, etc.) to your control's optional input signals
+4. Updates occur automatically through signal reactivity
+
+## Adding state signals
+
+The minimal controls shown above work, but they don't respond to form state. You can add optional input signals to make your controls react to disabled state, display validation errors, and track user interaction.
+
+Here's a comprehensive example that implements common state properties:
+
+```angular-ts
+import { Component, model, input, ChangeDetectionStrategy } from '@angular/core';
+import { FormValueControl } from '@angular/forms/signals';
+import type { ValidationError, DisabledReason } from '@angular/forms/signals';
+
+@Component({
+ selector: 'app-stateful-input',
+ template: `
+ @if (!hidden()) {
+
+ }
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class StatefulInput implements FormValueControl {
+ // Required
+ value = model('');
+
+ // Writable interaction state - control updates these
+ touched = model(false);
+
+ // Read-only state - form system manages these
+ disabled = input(false);
+ disabledReasons = input([]);
+ readonly = input(false);
+ hidden = input(false);
+ invalid = input(false);
+ errors = input([]);
+}
+```
+
+As a result, you can use the control with validation and state management:
+
+```angular-ts
+import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
+import { form, Field, required, email } from '@angular/forms/signals';
+import { StatefulInput } from './stateful-input';
+
+@Component({
+ imports: [Field, StatefulInput],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class Login {
+ loginModel = signal({ email: '' });
+
+ loginForm = form(this.loginModel, (schemaPath) => {
+ required(schemaPath.email, { message: 'Email is required' });
+ email(schemaPath.email, { message: 'Enter a valid email address' });
+ });
+}
+```
+
+When the user types an invalid email, the Field directive automatically updates `invalid()` and `errors()`. Your control can display the validation feedback.
+
+### Signal types for state properties
+
+Most state properties use `input()` (read-only from the form). Use `model()` for `touched` when your control updates it on user interaction. The `touched` property uniquely supports `model()`, `input()`, or `OutputRef` depending on your needs.
+
+## Value transformation
+
+Controls sometimes display values differently than the form model stores them - a date picker might display "January 15, 2024" while storing "2024-01-15", or a currency input might show "$1,234.56" while storing 1234.56.
+
+Use `computed()` signals (from `@angular/core`) to transform the model value for display, and handle input events to parse user input back to the storage format:
+
+```angular-ts
+import { Component, model, computed, ChangeDetectionStrategy } from '@angular/core';
+import { FormValueControl } from '@angular/forms/signals';
+
+@Component({
+ selector: 'app-currency-input',
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CurrencyInput implements FormValueControl {
+ value = model(0); // Stores numeric value (1234.56)
+
+ displayValue = computed(() => {
+ return this.value().toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // Shows "1,234.56"
+ });
+
+ handleInput(input: string) {
+ const num = parseFloat(input.replace(/[^0-9.]/g, ''));
+ if (!isNaN(num)) this.value.set(num);
+ }
+}
+```
+
+## Validation integration
+
+Controls display validation state but don't perform validation. Validation happens in the form schema - your control receives `invalid()` and `errors()` signals from the Field directive and displays them (as shown in the StatefulInput example above).
+
+The Field directive also passes validation constraint values like `required`, `min`, `max`, `minLength`, `maxLength`, and `pattern`. Your control can use these to enhance the UI:
+
+```ts
+export class NumberInput implements FormValueControl {
+ value = model(0);
+
+ // Constraint values from schema validation rules
+ required = input(false);
+ min = input(undefined);
+ max = input(undefined);
+}
+```
+
+When you add `min()` and `max()` validation rules to the schema, the Field directive passes these values to your control. Use them to apply HTML5 attributes or show constraint hints in your template.
+
+IMPORTANT: Don't implement validation logic in your control. Define validation rules in the form schema and let your control display the results:
+
+```typescript
+// Avoid: Validation in control
+export class BadControl implements FormValueControl {
+ value = model('')
+ isValid() { return this.value().length >= 8 } // Don't do this!
+}
+
+// Good: Validation in schema, control displays results
+accountForm = form(this.accountModel, schemaPath => {
+ minLength(schemaPath.password, 8, { message: 'Password must be at least 8 characters' })
+})
+```
+
+## Next steps
+
+This guide covered building custom controls that integrate with Signal Forms. Related guides explore other aspects of Signal Forms:
+
+- [Form Models guide](guide/forms/signals/models) - Creating and updating form models
+
+
+
diff --git a/adev-es/src/content/guide/forms/signals/custom-controls.md b/adev-es/src/content/guide/forms/signals/custom-controls.md
index 1ce7026..c7c712c 100644
--- a/adev-es/src/content/guide/forms/signals/custom-controls.md
+++ b/adev-es/src/content/guide/forms/signals/custom-controls.md
@@ -1,18 +1,18 @@
-# Custom Controls
+# Controles Personalizados
-NOTE: This guide assumes familiarity with [Signal Forms essentials](essentials/signal-forms).
+NOTA: Esta guía asume familiaridad con [Fundamentos de Signal Forms](essentials/signal-forms).
-The browser's built-in form controls (like input, select, textarea) handle common cases, but applications often need specialized inputs. A date picker with calendar UI, a rich text editor with formatting toolbar, or a tag selector with autocomplete all require custom implementations.
+Los controles de formulario integrados del navegador (como input, select, textarea) manejan casos comunes, pero las aplicaciones a menudo necesitan entradas especializadas. Un selector de fecha con UI de calendario, un editor de texto enriquecido con barra de herramientas de formato, o un selector de etiquetas con autocompletado, todos requieren implementaciones personalizadas.
-Signal Forms works with any component that implements specific interfaces. A **control interface** defines the properties and signals that allow your component to communicate with the form system. When your component implements one of these interfaces, the `[field]` directive automatically connects your control to form state, validation, and data binding.
+Signal Forms funciona con cualquier componente que implemente interfaces específicas. Una **interfaz de control** define las propiedades y signals que permiten a tu componente comunicarse con el sistema de formularios. Cuando tu componente implementa una de estas interfaces, la directiva `[field]` conecta automáticamente tu control al estado del formulario, validación, y enlace de datos.
-## Creating a basic custom control
+## Creando un control personalizado básico
-Let's start with a minimal implementation and add features as needed.
+Empecemos con una implementación mínima y agreguemos características según sea necesario.
-### Minimal input control
+### Control de entrada mínimo
-A basic custom input only needs to implement the `FormValueControl` interface and define the required `value` model signal.
+Un input personalizado básico solo necesita implementar la interfaz `FormValueControl` y definir el signal modelo `value` requerido.
```angular-ts
import { Component, model } from '@angular/core';
@@ -37,12 +37,12 @@ export class BasicInput implements FormValueControl {
}
```
-### Minimal checkbox control
+### Control de checkbox mínimo
-A checkbox-style control needs two things:
+Un control estilo checkbox necesita dos cosas:
-1. Implement the `FormCheckboxControl` interface so the `Field` directive will recognize it as a form control
-2. Provide a `checked` model signal
+1. Implementar la interfaz `FormCheckboxControl` para que la directiva `Field` lo reconozca como un control de formulario
+2. Proporcionar un signal modelo `checked`
```angular-ts
import { Component, model, ChangeDetectionStrategy } from '@angular/core';
@@ -71,9 +71,9 @@ export class BasicToggle implements FormCheckboxControl {
}
```
-### Using your custom control
+### Usando tu control personalizado
-Once you've created a control, you can use it anywhere you would use a built-in input by adding the `Field` directive to it:
+Una vez que hayas creado un control, puedes usarlo en cualquier lugar donde usarías un input integrado agregándole la directiva `Field`:
```angular-ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@@ -118,99 +118,99 @@ export class Registration {
}
```
-NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+NOTA: El parámetro callback del esquema (`schemaPath` en estos ejemplos) es un objeto `SchemaPathTree` que proporciona rutas a todos los campos en tu formulario. Puedes nombrar este parámetro como desees.
-The `[field]` directive works identically for custom controls and built-in inputs. Signal Forms treats them the same - validation runs, state updates, and data binding works automatically.
+La directiva `[field]` funciona de manera idéntica para controles personalizados e inputs integrados. Signal Forms los trata igual - la validación se ejecuta, el estado se actualiza, y el enlace de datos funciona automáticamente.
-## Understanding control interfaces
+## Entendiendo las interfaces de control
-Now that you've seen custom controls in action, let's explore how they integrate with Signal Forms.
+Ahora que has visto los controles personalizados en acción, exploremos cómo se integran con Signal Forms.
-### Control interfaces
+### Interfaces de control
-The `BasicInput` and `BasicToggle` components you created implement specific control interfaces that tell Signal Forms how to interact with them.
+Los componentes `BasicInput` y `BasicToggle` que creaste implementan interfaces de control específicas que le dicen a Signal Forms cómo interactuar con ellos.
#### FormValueControl
-`FormValueControl` is the interface for most input types - text inputs, number inputs, date pickers, select dropdowns, and any control that edits a single value. When your component implements this interface:
+`FormValueControl` es la interfaz para la mayoría de tipos de entrada - inputs de texto, inputs de número, selectores de fecha, dropdowns select, y cualquier control que edite un solo valor. Cuando tu componente implementa esta interfaz:
-- **Required property**: Your component must provide a `value` model signal
-- **What the Field directive does**: Binds the form field's value to your control's `value` signal
+- **Propiedad requerida**: Tu componente debe proporcionar un signal modelo `value`
+- **Lo que hace la directiva Field**: Vincula el valor del campo del formulario al signal `value` de tu control
-IMPORTANT: Controls implementing `FormValueControl` must NOT have a `checked` property
+IMPORTANTE: Los controles que implementan `FormValueControl` NO deben tener una propiedad `checked`
#### FormCheckboxControl
-`FormCheckboxControl` is the interface for checkbox-like controls - toggles, switches, and any control that represents a boolean on/off state. When your component implements this interface:
+`FormCheckboxControl` es la interfaz para controles tipo checkbox - toggles, switches, y cualquier control que represente un estado booleano de encendido/apagado. Cuando tu componente implementa esta interfaz:
-- **Required property**: Your component must provide a `checked` model signal
-- **What the Field directive does**: Binds the form field's value to your control's `checked` signal
+- **Propiedad requerida**: Tu componente debe proporcionar un signal modelo `checked`
+- **Lo que hace la directiva Field**: Vincula el valor del campo del formulario al signal `checked` de tu control
-IMPORTANT: Controls implementing `FormCheckboxControl` must NOT have a `value` property
+IMPORTANTE: Los controles que implementan `FormCheckboxControl` NO deben tener una propiedad `value`
-### Optional state properties
+### Propiedades de estado opcionales
-Both `FormValueControl` and `FormCheckboxControl` extend `FormUiControl` - a base interface that provides optional properties for integrating with form state.
+Tanto `FormValueControl` como `FormCheckboxControl` extienden `FormUiControl` - una interfaz base que proporciona propiedades opcionales para integrarse con el estado del formulario.
-All properties are optional. Implement only what your control needs.
+Todas las propiedades son opcionales. Implementa solo lo que tu control necesita.
-#### Interaction state
+#### Estado de interacción
-Track when users interact with your control:
+Rastrea cuando los usuarios interactúan con tu control:
-| Property | Purpose |
-| --------- | ------------------------------------------------ |
-| `touched` | Whether the user has interacted with the field |
-| `dirty` | Whether the value differs from its initial state |
+| Propiedad | Propósito |
+| --------- | ------------------------------------------------------------- |
+| `touched` | Si el usuario ha interactuado con el campo |
+| `dirty` | Si el valor difiere de su estado inicial |
-#### Validation state
+#### Estado de validación
-Display validation feedback to users:
+Muestra comentarios de validación a los usuarios:
-| Property | Purpose |
-| --------- | --------------------------------------- |
-| `errors` | Array of current validation errors |
-| `valid` | Whether the field is valid |
-| `invalid` | Whether the field has validation errors |
-| `pending` | Whether async validation is in progress |
+| Propiedad | Propósito |
+| --------- | ------------------------------------------- |
+| `errors` | Array de errores de validación actuales |
+| `valid` | Si el campo es válido |
+| `invalid` | Si el campo tiene errores de validación |
+| `pending` | Si la validación asíncrona está en progreso |
-#### Availability state
+#### Estado de disponibilidad
-Control whether users can interact with your field:
+Controla si los usuarios pueden interactuar con tu campo:
-| Property | Purpose |
-| ----------------- | -------------------------------------------------------- |
-| `disabled` | Whether the field is disabled |
-| `disabledReasons` | Reasons why the field is disabled |
-| `readonly` | Whether the field is readonly (visible but not editable) |
-| `hidden` | Whether the field is hidden from view |
+| Propiedad | Propósito |
+| ----------------- | ------------------------------------------------------------------- |
+| `disabled` | Si el campo está deshabilitado |
+| `disabledReasons` | Razones por las que el campo está deshabilitado |
+| `readonly` | Si el campo es de solo lectura (visible pero no editable) |
+| `hidden` | Si el campo está oculto de la vista |
-NOTE: `disabledReasons` is an array of `DisabledReason` objects. Each object has a `field` property (reference to the field tree) and an optional `message` property. Access the message via `reason.message`.
+NOTA: `disabledReasons` es un array de objetos `DisabledReason`. Cada objeto tiene una propiedad `field` (referencia al árbol de campo) y una propiedad `message` opcional. Accede al mensaje a través de `reason.message`.
-#### Validation constraints
+#### Restricciones de validación
-Receive validation constraint values from the form:
+Recibe valores de restricción de validación del formulario:
-| Property | Purpose |
-| ----------- | ---------------------------------------------------- |
-| `required` | Whether the field is required |
-| `min` | Minimum numeric value (`undefined` if no constraint) |
-| `max` | Maximum numeric value (`undefined` if no constraint) |
-| `minLength` | Minimum string length (undefined if no constraint) |
-| `maxLength` | Maximum string length (undefined if no constraint) |
-| `pattern` | Array of regular expression patterns to match |
+| Propiedad | Propósito |
+| ----------- | --------------------------------------------------------------------------- |
+| `required` | Si el campo es requerido |
+| `min` | Valor numérico mínimo (`undefined` si no hay restricción) |
+| `max` | Valor numérico máximo (`undefined` si no hay restricción) |
+| `minLength` | Longitud mínima de cadena (undefined si no hay restricción) |
+| `maxLength` | Longitud máxima de cadena (undefined si no hay restricción) |
+| `pattern` | Array de patrones de expresión regular a coincidir |
-#### Field metadata
+#### Metadatos de campo
-| Property | Purpose |
-| -------- | ------------------------------------------------------------------ |
-| `name` | The field's name attribute (which is unique across forms and apps) |
+| Propiedad | Propósito |
+| --------- | ---------------------------------------------------------------------------------- |
+| `name` | El atributo name del campo (que es único entre formularios y aplicaciones) |
-The "[Adding state signals](#adding-state-signals)" section below shows how to implement these properties in your controls.
+La sección "[Agregando signals de estado](#agregando-signals-de-estado)" a continuación muestra cómo implementar estas propiedades en tus controles.
-### How the Field directive works
+### Cómo funciona la directiva Field
-The `[field]` directive detects which interface your control implements and automatically binds the appropriate signals:
+La directiva `[field]` detecta qué interfaz implementa tu control y automáticamente vincula los signals apropiados:
```angular-ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@@ -241,20 +241,20 @@ export class MyForm {
}
```
-TIP: For complete coverage of creating and managing form models, see the [Form Models guide](guide/forms/signals/models).
+CONSEJO: Para una cobertura completa de la creación y gestión de modelos de formulario, consulta la [guía de Modelos de Formulario](guide/forms/signals/models).
-When you bind `[field]="userForm.username"`, the Field directive:
+Cuando vinculas `[field]="userForm.username"`, la directiva Field:
-1. Detects your control implements `FormValueControl`
-2. Internally accesses `userForm.username().value()` and binds it to your control's `value` model signal
-3. Binds form state signals (`disabled()`, `errors()`, etc.) to your control's optional input signals
-4. Updates occur automatically through signal reactivity
+1. Detecta que tu control implementa `FormValueControl`
+2. Accede internamente a `userForm.username().value()` y lo vincula al signal modelo `value` de tu control
+3. Vincula signals de estado del formulario (`disabled()`, `errors()`, etc.) a los signals de entrada opcionales de tu control
+4. Las actualizaciones ocurren automáticamente a través de la reactividad de signals
-## Adding state signals
+## Agregando signals de estado
-The minimal controls shown above work, but they don't respond to form state. You can add optional input signals to make your controls react to disabled state, display validation errors, and track user interaction.
+Los controles mínimos mostrados arriba funcionan, pero no responden al estado del formulario. Puedes agregar signals de entrada opcionales para hacer que tus controles reaccionen al estado deshabilitado, muestren errores de validación, y rastreen la interacción del usuario.
-Here's a comprehensive example that implements common state properties:
+Aquí hay un ejemplo completo que implementa propiedades de estado comunes:
```angular-ts
import { Component, model, input, ChangeDetectionStrategy } from '@angular/core';
@@ -314,7 +314,7 @@ export class StatefulInput implements FormValueControl {
}
```
-As a result, you can use the control with validation and state management:
+Como resultado, puedes usar el control con validación y gestión de estado:
```angular-ts
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
@@ -343,17 +343,17 @@ export class Login {
}
```
-When the user types an invalid email, the Field directive automatically updates `invalid()` and `errors()`. Your control can display the validation feedback.
+Cuando el usuario escribe un email inválido, la directiva Field actualiza automáticamente `invalid()` y `errors()`. Tu control puede mostrar los comentarios de validación.
-### Signal types for state properties
+### Tipos de signal para propiedades de estado
-Most state properties use `input()` (read-only from the form). Use `model()` for `touched` when your control updates it on user interaction. The `touched` property uniquely supports `model()`, `input()`, or `OutputRef` depending on your needs.
+La mayoría de las propiedades de estado usan `input()` (solo lectura desde el formulario). Usa `model()` para `touched` cuando tu control lo actualice en la interacción del usuario. La propiedad `touched` admite de manera única `model()`, `input()`, o `OutputRef` dependiendo de tus necesidades.
-## Value transformation
+## Transformación de valores
-Controls sometimes display values differently than the form model stores them - a date picker might display "January 15, 2024" while storing "2024-01-15", or a currency input might show "$1,234.56" while storing 1234.56.
+Los controles a veces muestran valores de manera diferente a como el modelo del formulario los almacena - un selector de fecha podría mostrar "January 15, 2024" mientras almacena "2024-01-15", o un input de moneda podría mostrar "$1,234.56" mientras almacena 1234.56.
-Use `computed()` signals (from `@angular/core`) to transform the model value for display, and handle input events to parse user input back to the storage format:
+Usa signals `computed()` (de `@angular/core`) para transformar el valor del modelo para visualización, y maneja eventos de entrada para parsear la entrada del usuario de vuelta al formato de almacenamiento:
```angular-ts
import { Component, model, computed, ChangeDetectionStrategy } from '@angular/core';
@@ -384,11 +384,11 @@ export class CurrencyInput implements FormValueControl {
}
```
-## Validation integration
+## Integración de validación
-Controls display validation state but don't perform validation. Validation happens in the form schema - your control receives `invalid()` and `errors()` signals from the Field directive and displays them (as shown in the StatefulInput example above).
+Los controles muestran el estado de validación pero no realizan validación. La validación ocurre en el esquema del formulario - tu control recibe signals `invalid()` y `errors()` de la directiva Field y los muestra (como se muestra en el ejemplo de StatefulInput arriba).
-The Field directive also passes validation constraint values like `required`, `min`, `max`, `minLength`, `maxLength`, and `pattern`. Your control can use these to enhance the UI:
+La directiva Field también pasa valores de restricción de validación como `required`, `min`, `max`, `minLength`, `maxLength`, y `pattern`. Tu control puede usarlos para mejorar la UI:
```ts
export class NumberInput implements FormValueControl {
@@ -401,28 +401,28 @@ export class NumberInput implements FormValueControl {
}
```
-When you add `min()` and `max()` validation rules to the schema, the Field directive passes these values to your control. Use them to apply HTML5 attributes or show constraint hints in your template.
+Cuando agregas reglas de validación `min()` y `max()` al esquema, la directiva Field pasa estos valores a tu control. Úsalos para aplicar atributos HTML5 o mostrar sugerencias de restricción en tu plantilla.
-IMPORTANT: Don't implement validation logic in your control. Define validation rules in the form schema and let your control display the results:
+IMPORTANTE: No implementes lógica de validación en tu control. Define reglas de validación en el esquema del formulario y deja que tu control muestre los resultados:
```typescript
-// Avoid: Validation in control
+// Evitar: Validación en el control
export class BadControl implements FormValueControl {
value = model('')
isValid() { return this.value().length >= 8 } // Don't do this!
}
-// Good: Validation in schema, control displays results
+// Bien: Validación en el esquema, el control muestra resultados
accountForm = form(this.accountModel, schemaPath => {
minLength(schemaPath.password, 8, { message: 'Password must be at least 8 characters' })
})
```
-## Next steps
+## Próximos pasos
-This guide covered building custom controls that integrate with Signal Forms. Related guides explore other aspects of Signal Forms:
+Esta guía cubrió la construcción de controles personalizados que se integran con Signal Forms. Las guías relacionadas exploran otros aspectos de Signal Forms:
-- [Form Models guide](guide/forms/signals/models) - Creating and updating form models
+- [Guía de Modelos de Formulario](guide/forms/signals/models) - Creando y actualizando modelos de formulario
-
-
+
+
diff --git a/adev-es/src/content/guide/forms/signals/field-state-management.en.md b/adev-es/src/content/guide/forms/signals/field-state-management.en.md
new file mode 100644
index 0000000..be707bd
--- /dev/null
+++ b/adev-es/src/content/guide/forms/signals/field-state-management.en.md
@@ -0,0 +1,695 @@
+# Field state management
+
+Signal Forms' field state allows you to react to user interactions by providing reactive signals for validation status (such as `valid`, `invalid`, `errors`), interaction tracking (such as `touched`, `dirty`), and availability (such as `disabled`, `hidden`).
+
+## Understanding field state
+
+When you create a form with the `form()` function, it returns a **field tree** - an object structure that mirrors your form model. Each field in the tree is accessible via dot notation (like `form.email`).
+
+### Accessing field state
+
+When you call any field in the field tree as a function (like `form.email()`), it returns a `FieldState` object containing reactive signals that track the field's validation, interaction, and availability state. For example, the `invalid()` signal tells you whether the field has validation errors:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, required, email } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-registration',
+ imports: [Field],
+ template: `
+
+
+ @if (registrationForm.email().invalid()) {
+
Email has validation errors:
+
+ @for (error of registrationForm.email().errors(); track error) {
+
{{ error.message }}
+ }
+
+ }
+ `
+})
+export class Registration {
+ registrationModel = signal({
+ email: '',
+ password: ''
+ })
+
+ registrationForm = form(this.registrationModel, (schemaPath) => {
+ required(schemaPath.email, { message: 'Email is required' })
+ email(schemaPath.email, { message: 'Enter a valid email address' })
+ })
+}
+```
+
+In this example, the template checks `registrationForm.email().invalid()` to determine whether to display an error message.
+
+### Field state signals
+
+The most commonly used signal is `value()`, a [writable signal](guide/forms/signals/models#updating-models) that provides access to the field's current value:
+
+```ts
+const emailValue = registrationForm.email().value()
+console.log(emailValue) // Current email string
+```
+
+Beyond `value()`, field state includes signals for validation, interaction tracking, and availability control:
+
+| Category | Signal | Description |
+| --------------------------------------- | ------------ | --------------------------------------------------------------------------------- |
+| **[Validation](#validation-state)** | `valid()` | Field passes all validation rules and has no pending validators |
+| | `invalid()` | Field has validation errors |
+| | `errors()` | Array of validation error objects |
+| | `pending()` | Async validation in progress |
+| **[Interaction](#interaction-state)** | `touched()` | User has focused and blurred the field (if interactive) |
+| | `dirty()` | User has modified the field (if interactive), even if value matches initial state |
+| **[Availability](#availability-state)** | `disabled()` | Field is disabled and doesn't affect parent form state |
+| | `hidden()` | Indicates field should be hidden; visibility in template is controlled with `@if` |
+| | `readonly()` | Field is readonly and doesn't affect parent form state |
+
+These signals enable you to build responsive form user experiences that react to user behavior. The sections below explore each category in detail.
+
+## Validation state
+
+Validation state signals tell you whether a field is valid and what errors it contains.
+
+NOTE: This guide focuses on **using** validation state in your templates and logic (such as reading `valid()`, `invalid()`, `errors()` to display feedback). For information on **defining** validation rules and creating custom validators, see the Validation guide (coming soon).
+
+### Checking validity
+
+Use `valid()` and `invalid()` to check validation status:
+
+```angular-ts
+@Component({
+ template: `
+
+
+ @if (loginForm.email().invalid()) {
+
Email is invalid
+ } @if (loginForm.email().valid()) {
+
Email looks good
+ }
+ `
+})
+export class Login {
+ loginModel = signal({ email: '', password: '' })
+ loginForm = form(this.loginModel)
+}
+```
+
+| Signal | Returns `true` when |
+| ----------- | --------------------------------------------------------------- |
+| `valid()` | Field passes all validation rules and has no pending validators |
+| `invalid()` | Field has validation errors |
+
+When checking validity in code, use `invalid()` instead of `!valid()` if you want to distinguish between "has errors" and "validation pending." The reason for this is that both `valid()` and `invalid()` can be `false` simultaneously when async validation is pending because the field isn't valid yet since validation not complete and is also isn't invalid since no errors have been found yet.
+
+### Reading validation errors
+
+Access the array of validation errors with `errors()`. Each error object contains:
+
+| Property | Description |
+| --------- | --------------------------------------------------------------- |
+| `kind` | The validation rule that failed (such as "required" or "email") |
+| `message` | Optional human-readable error message |
+| `field` | Reference to the `FieldTree` where the error occurred |
+
+NOTE: The `message` property is optional. Validators can provide custom error messages, but if not specified, you may need to map error `kind` values to your own messages.
+
+Here's an example of how to display errors in your template:
+
+```angular-ts
+@Component({
+ template: `
+
+
+ @if (loginForm.email().errors().length > 0) {
+
+ @for (error of loginForm.email().errors(); track error) {
+
{{ error.message }}
+ }
+
+ }
+ `
+})
+```
+
+This approach loops through all errors for a field, displaying each error message to the user.
+
+### Pending validation
+
+The `pending()` signal indicates async validation is in progress:
+
+```angular-ts
+@Component({
+ template: `
+
+
+ @if (signupForm.email().pending()) {
+
+ }
+ `
+})
+```
+
+This signal enables you to show loading states while async validation executes.
+
+## Interaction state
+
+Interaction state tracks whether users have interacted with fields, enabling patterns like "show errors only after the user has touched a field."
+
+### Touched state
+
+The `touched()` signal tracks whether a user has focused and then blurred a field. It becomes `true` when a user focuses and then blurs a field through user interaction (not programmatically). Hidden, disabled, and readonly fields are non-interactive and don't become touched from user interactions.
+
+### Dirty state
+
+Forms often need to detect whether data has actually changed - for example, to warn users about unsaved changes or to enable a save button only when necessary. The `dirty()` signal tracks whether the user has modified the field.
+
+The `dirty()` signal becomes `true` when the user modifies an interactive field's value, and remains `true` even if the value is changed back to match the initial value:
+
+```angular-ts
+@Component({
+ template: `
+
+ `
+})
+export class Profile {
+ profileModel = signal({ name: 'Alice', bio: 'Developer' })
+ profileForm = form(this.profileModel)
+}
+```
+
+Use `dirty()` for "unsaved changes" warnings or to enable save buttons only when data has changed.
+
+### Touched vs dirty
+
+These signals track different user interactions:
+
+| Signal | When it becomes true |
+| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| `touched()` | User has focused and blurred an interactive field (even if they didn't change anything) |
+| `dirty()` | User has modified an interactive field (even if they never blurred it, and even if the current value matches the initial value) |
+
+A field can be in different combinations:
+
+| State | Scenario |
+| ---------------------- | --------------------------------------------------------- |
+| Touched but not dirty | User focused and blurred the field but made no changes |
+| Both touched and dirty | User focused the field, changed the value, and blurred it |
+
+NOTE: Hidden, disabled, and readonly fields are non-interactive - they don't become touched or dirty from user interactions.
+
+## Availability state
+
+Availability state signals control whether fields are interactive, editable, or visible. Disabled, hidden, and readonly fields are non-interactive. They don't affect whether their parent form is valid, touched, or dirty.
+
+### Disabled fields
+
+The `disabled()` signal indicates whether a field accepts user input. Disabled fields appear in the UI but users cannot interact with them.
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, disabled } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-order',
+ imports: [Field],
+ template: `
+
+
+
+ @if (orderForm.couponCode().disabled()) {
+
Coupon code is only available for orders over $50
+ }
+ `
+})
+export class Order {
+ orderModel = signal({
+ total: 25,
+ couponCode: ''
+ })
+
+ orderForm = form(this.orderModel, schemaPath => {
+ disabled(schemaPath.couponCode, ({valueOf}) => valueOf(schemaPath.total) < 50)
+ })
+}
+```
+
+In this example, we use `valueOf(schemaPath.total)` to check the value of the `total` field to determine whether `couponCode` should be disabled.
+
+NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+
+When defining rules like `disabled()`, `hidden()`, or `readonly()`, the logic callback receives a `FieldContext` object that is typically destructured (such as `({valueOf})`). Two methods commonly used in validation rules are:
+
+- `valueOf(schemaPath.otherField)` - Read the value of another field in the form
+- `value()` - A signal containing the value of the field the rule is applied to
+
+Disabled fields don't contribute to the parent form's validation state. Even if a disabled field would be invalid, the parent form can still be valid. The `disabled()` state affects interactivity and validation, but does not change the field's value.
+
+### Hidden fields
+
+The `hidden()` signal indicates whether a field is conditionally hidden. Use `hidden()` with `@if` to show or hide fields based on conditions:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, hidden } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-profile',
+ imports: [Field],
+ template: `
+
+
+ @if (!profileForm.publicUrl().hidden()) {
+
+ }
+ `
+})
+export class Profile {
+ profileModel = signal({
+ isPublic: false,
+ publicUrl: ''
+ })
+
+ profileForm = form(this.profileModel, schemaPath => {
+ hidden(schemaPath.publicUrl, ({valueOf}) => !valueOf(schemaPath.isPublic))
+ })
+}
+```
+
+Hidden fields don't participate in validation. If a required field is hidden, it won't prevent form submission. The `hidden()` state affects availability and validation, but does not change the field's value.
+
+### Readonly fields
+
+The `readonly()` signal indicates whether a field is readonly. Readonly fields display their value but users cannot edit them:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, readonly } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-account',
+ imports: [Field],
+ template: `
+
+
+
+ `
+})
+export class Account {
+ accountModel = signal({
+ username: 'johndoe',
+ email: 'john@example.com'
+ })
+
+ accountForm = form(this.accountModel, schemaPath => {
+ readonly(schemaPath.username)
+ })
+}
+```
+
+NOTE: The `[field]` directive automatically binds the `readonly` attribute based on the field's `readonly()` state, so you don't need to manually add `[readonly]="field().readonly()"`.
+
+Like disabled and hidden fields, readonly fields are non-interactive and don't affect parent form state. The `readonly()` state affects editability and validation, but does not change the field's value.
+
+### When to use each
+
+| State | Use when | User can see it | User can interact | Contributes to validation |
+| ------------ | ------------------------------------------------------------------- | --------------- | ----------------- | ------------------------- |
+| `disabled()` | Field temporarily unavailable (such as based on other field values) | Yes | No | No |
+| `hidden()` | Field not relevant in current context | No (with @if) | No | No |
+| `readonly()` | Value should be visible but not editable | Yes | No | No |
+
+## Form-level state
+
+The root form is also a field in the field tree. When you call it as a function, it also returns a `FieldState` object that aggregates the state of all child fields.
+
+### Accessing form state
+
+```angular-ts
+@Component({
+ template: `
+
+ `
+})
+export class Login {
+ loginModel = signal({ email: '', password: '' })
+ loginForm = form(this.loginModel)
+}
+```
+
+In this example, the form is valid only when all child fields are valid. This allows you to enable/disable submit buttons based on overall form validity.
+
+### Form-level signals
+
+Because the root form is a field, it has the same signals (such as `valid()`, `invalid()`, `touched()`, `dirty()`, etc.).
+
+| Signal | Form-level behavior |
+| ----------- | -------------------------------------------------------------- |
+| `valid()` | All interactive fields are valid and no validators are pending |
+| `invalid()` | At least one interactive field has validation errors |
+| `pending()` | At least one interactive field has pending async validation |
+| `touched()` | User has touched at least one interactive field |
+| `dirty()` | User has modified at least one interactive field |
+
+### When to use form-level vs field-level
+
+**Use form-level state for:**
+
+- Submit button enabled/disabled state
+- "Save" button state
+- Overall form validity checks
+- Unsaved changes warnings
+
+**Use field-level state for:**
+
+- Individual field error messages
+- Field-specific styling
+- Per-field validation feedback
+- Conditional field availability
+
+## State propagation
+
+Field state propagates from child fields up through parent field groups to the root form.
+
+### How child state affects parent forms
+
+When a child field becomes invalid, its parent field group becomes invalid, and so does the root form. When a child becomes touched or dirty, the parent field group and root form reflect that change. This aggregation allows you to check validity at any level - field or entire form.
+
+```ts
+const userModel = signal({
+ profile: {
+ firstName: '',
+ lastName: ''
+ },
+ address: {
+ street: '',
+ city: ''
+ }
+})
+
+const userForm = form(userModel)
+
+// If firstName is invalid, profile is invalid
+userForm.profile.firstName().invalid() === true
+// → userForm.profile().invalid() === true
+// → userForm().invalid() === true
+```
+
+### Hidden, disabled, and readonly fields
+
+Hidden, disabled, and readonly fields are non-interactive and don't affect parent form state:
+
+```ts
+const orderModel = signal({
+ customerName: '',
+ requiresShipping: false,
+ shippingAddress: ''
+})
+
+const orderForm = form(orderModel, schemaPath => {
+ hidden(schemaPath.shippingAddress, ({valueOf}) => !valueOf(schemaPath.requiresShipping))
+})
+```
+
+In this example, when `shippingAddress` is hidden, it doesn't affect form validity. As a result, even if `shippingAddress` is empty and required, the form can be valid.
+
+This behavior prevents hidden, disabled, or readonly fields from blocking form submission or affecting validation, touched, and dirty state.
+
+## Using state in templates
+
+Field state signals integrate seamlessly with Angular templates, enabling reactive form user experiences without manual event handling.
+
+### Conditional error display
+
+Show errors only after a user has interacted with a field:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, email } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-signup',
+ imports: [Field],
+ template: `
+
+
+ @if (signupForm.email().touched() && signupForm.email().invalid()) {
+
{{ signupForm.email().errors()[0].message }}
+ }
+ `
+})
+export class Signup {
+ signupModel = signal({ email: '', password: '' })
+
+ signupForm = form(this.signupModel, schemaPath => {
+ email(schemaPath.email)
+ })
+}
+```
+
+This pattern prevents showing errors before users have had a chance to interact with the field. Errors appear only after the user has focused and then left the field.
+
+### Conditional field availability
+
+Use the `hidden()` signal with `@if` to show or hide fields conditionally:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, hidden } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-order',
+ imports: [Field],
+ template: `
+
+
+ @if (!orderForm.shippingAddress().hidden()) {
+
+ }
+ `
+})
+export class Order {
+ orderModel = signal({
+ requiresShipping: false,
+ shippingAddress: ''
+ })
+
+ orderForm = form(this.orderModel, schemaPath => {
+ hidden(schemaPath.shippingAddress, ({valueOf}) => !valueOf(schemaPath.requiresShipping))
+ })
+}
+```
+
+Hidden fields don't participate in validation, allowing the form to be submitted even if the hidden field would otherwise be invalid.
+
+## Using field state in component logic
+
+Field state signals work with Angular's reactive primitives like `computed()` and `effect()` for advanced form logic.
+
+### Validation checks before submission
+
+Check form validity in component methods:
+
+```ts
+export class Registration {
+ registrationModel = signal({
+ username: '',
+ email: '',
+ password: ''
+ })
+
+ registrationForm = form(this.registrationModel)
+
+ async onSubmit() {
+ // Wait for any pending async validation
+ if (this.registrationForm().pending()) {
+ console.log('Waiting for validation...')
+ return
+ }
+
+ // Guard against invalid submissions
+ if (this.registrationForm().invalid()) {
+ console.error('Form is invalid')
+ return
+ }
+
+ const data = this.registrationModel()
+ await this.api.register(data)
+ }
+}
+```
+
+This ensures only valid, fully-validated data reaches your API.
+
+### Derived state with computed
+
+Create computed signals based on field state to automatically update when the underlying field state changes:
+
+```ts
+export class Password {
+ passwordModel = signal({ password: '', confirmPassword: '' })
+ passwordForm = form(this.passwordModel)
+
+ // Compute password strength indicator
+ passwordStrength = computed(() => {
+ const password = this.passwordForm.password().value()
+ if (password.length < 8) return 'weak'
+ if (password.length < 12) return 'medium'
+ return 'strong'
+ })
+
+ // Check if all required fields are filled
+ allFieldsFilled = computed(() => {
+ return (
+ this.passwordForm.password().value().length > 0 &&
+ this.passwordForm.confirmPassword().value().length > 0
+ )
+ })
+}
+```
+
+### Programmatic state changes
+
+While field state typically updates through user interactions (typing, focusing, blurring), you sometimes need to control it programmatically. Common scenarios include form submission and resetting forms.
+
+#### Form submission
+
+When a user submits a form, use the `submit()` function to handle validation and reveal errors:
+
+```ts
+import { Component, signal } from '@angular/core'
+import { form, submit, required, email } from '@angular/forms/signals'
+
+export class Registration {
+ registrationModel = signal({ username: '', email: '', password: '' })
+
+ registrationForm = form(this.registrationModel, schemaPath => {
+ required(schemaPath.username)
+ email(schemaPath.email)
+ required(schemaPath.password)
+ })
+
+ onSubmit() {
+ submit(this.registrationForm, () => {
+ this.submitToServer()
+ })
+ }
+
+ submitToServer() {
+ // Send data to server
+ }
+}
+```
+
+The `submit()` function automatically marks all fields as touched (revealing validation errors) and only executes your callback if the form is valid.
+
+#### Resetting forms after submission
+
+After successfully submitting a form, you may want to return it to its initial state - clearing both user interaction history and field values. The `reset()` method clears the touched and dirty flags but doesn't change field values, so you need to update your model separately:
+
+```ts
+export class Contact {
+ contactModel = signal({ name: '', email: '', message: '' })
+ contactForm = form(this.contactModel)
+
+ async onSubmit() {
+ if (!this.contactForm().valid()) return
+
+ await this.api.sendMessage(this.contactModel())
+
+ // Clear interaction state (touched, dirty)
+ this.contactForm().reset()
+
+ // Clear values
+ this.contactModel.set({ name: '', email: '', message: '' })
+ }
+}
+```
+
+This two-step reset ensures the form is ready for new input without showing stale error messages or dirty state indicators.
+
+## Styling based on validation state
+
+You can apply custom styles to your form by binding CSS classes based on the validation state:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, email } from '@angular/forms/signals'
+
+@Component({
+ template: `
+
+ `,
+ styles: `
+ input.is-invalid {
+ border: 2px solid red;
+ background-color: white;
+ }
+
+ input.is-valid {
+ border: 2px solid green;
+ }
+ `
+})
+export class StyleExample {
+ model = signal({ email: '' })
+
+ form = form(this.model, schemaPath => {
+ email(schemaPath.email)
+ })
+}
+```
+
+Checking both `touched()` and validation state ensures styles only appear after the user has interacted with the field.
+
+## Next steps
+
+Here are other related guides on Signal Forms:
+
+- [Form Models guide](guide/forms/signals/models) - Creating models and updating values
+- Validation guide - Defining validation rules and custom validators (coming soon)
diff --git a/adev-es/src/content/guide/forms/signals/field-state-management.md b/adev-es/src/content/guide/forms/signals/field-state-management.md
index be707bd..1e12493 100644
--- a/adev-es/src/content/guide/forms/signals/field-state-management.md
+++ b/adev-es/src/content/guide/forms/signals/field-state-management.md
@@ -1,14 +1,14 @@
-# Field state management
+# Gestión de estado de campos
-Signal Forms' field state allows you to react to user interactions by providing reactive signals for validation status (such as `valid`, `invalid`, `errors`), interaction tracking (such as `touched`, `dirty`), and availability (such as `disabled`, `hidden`).
+El estado de campos de Signal Forms te permite reaccionar a las interacciones del usuario proporcionando signals reactivos para el estado de validación (como `valid`, `invalid`, `errors`), rastreo de interacción (como `touched`, `dirty`) y disponibilidad (como `disabled`, `hidden`).
-## Understanding field state
+## Entendiendo el estado de campos
-When you create a form with the `form()` function, it returns a **field tree** - an object structure that mirrors your form model. Each field in the tree is accessible via dot notation (like `form.email`).
+Cuando creas un formulario con la función `form()`, retorna un **field tree** - una estructura de objeto que refleja tu modelo de formulario. Cada campo en el árbol es accesible mediante notación de punto (como `form.email`).
-### Accessing field state
+### Accediendo al estado del campo
-When you call any field in the field tree as a function (like `form.email()`), it returns a `FieldState` object containing reactive signals that track the field's validation, interaction, and availability state. For example, the `invalid()` signal tells you whether the field has validation errors:
+Cuando llamas a cualquier campo en el field tree como una función (como `form.email()`), retorna un objeto `FieldState` que contiene signals reactivos que rastrean la validación del campo, interacción y estado de disponibilidad. Por ejemplo, el signal `invalid()` te dice si el campo tiene errores de validación:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -43,42 +43,42 @@ export class Registration {
}
```
-In this example, the template checks `registrationForm.email().invalid()` to determine whether to display an error message.
+En este ejemplo, la plantilla verifica `registrationForm.email().invalid()` para determinar si se debe mostrar un mensaje de error.
-### Field state signals
+### Signals de estado de campo
-The most commonly used signal is `value()`, a [writable signal](guide/forms/signals/models#updating-models) that provides access to the field's current value:
+El signal más comúnmente usado es `value()`, un [signal editable](guide/forms/signals/models#updating-models) que proporciona acceso al valor actual del campo:
```ts
const emailValue = registrationForm.email().value()
console.log(emailValue) // Current email string
```
-Beyond `value()`, field state includes signals for validation, interaction tracking, and availability control:
+Más allá de `value()`, el estado del campo incluye signals para validación, rastreo de interacción y control de disponibilidad:
-| Category | Signal | Description |
-| --------------------------------------- | ------------ | --------------------------------------------------------------------------------- |
-| **[Validation](#validation-state)** | `valid()` | Field passes all validation rules and has no pending validators |
-| | `invalid()` | Field has validation errors |
-| | `errors()` | Array of validation error objects |
-| | `pending()` | Async validation in progress |
-| **[Interaction](#interaction-state)** | `touched()` | User has focused and blurred the field (if interactive) |
-| | `dirty()` | User has modified the field (if interactive), even if value matches initial state |
-| **[Availability](#availability-state)** | `disabled()` | Field is disabled and doesn't affect parent form state |
-| | `hidden()` | Indicates field should be hidden; visibility in template is controlled with `@if` |
-| | `readonly()` | Field is readonly and doesn't affect parent form state |
+| Categoría | Signal | Descripción |
+| --------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------ |
+| **[Estado de validación](#estado-de-validación)** | `valid()` | El campo pasa todas las reglas de validación y no tiene validadores pendientes |
+| | `invalid()` | El campo tiene errores de validación |
+| | `errors()` | Array de objetos de error de validación |
+| | `pending()` | Validación asíncrona en progreso |
+| **[Estado de interacción](#estado-de-interacción)** | `touched()` | El usuario ha enfocado y desenfocado el campo (si es interactivo) |
+| | `dirty()` | El usuario ha modificado el campo (si es interactivo), incluso si el valor coincide con el estado inicial |
+| **[Estado de disponibilidad](#estado-de-disponibilidad)** | `disabled()` | El campo está deshabilitado y no afecta el estado del formulario padre |
+| | `hidden()` | Indica que el campo debe estar oculto; la visibilidad en la plantilla se controla con `@if` |
+| | `readonly()` | El campo es de solo lectura y no afecta el estado del formulario padre |
-These signals enable you to build responsive form user experiences that react to user behavior. The sections below explore each category in detail.
+Estos signals te permiten construir experiencias de usuario de formularios responsivas que reaccionan al comportamiento del usuario. Las secciones a continuación exploran cada categoría en detalle.
-## Validation state
+## Estado de validación
-Validation state signals tell you whether a field is valid and what errors it contains.
+Los signals de estado de validación te indican si un campo es válido y qué errores contiene.
-NOTE: This guide focuses on **using** validation state in your templates and logic (such as reading `valid()`, `invalid()`, `errors()` to display feedback). For information on **defining** validation rules and creating custom validators, see the Validation guide (coming soon).
+NOTA: Esta guía se enfoca en **usar** el estado de validación en tus plantillas y lógica (como leer `valid()`, `invalid()`, `errors()` para mostrar retroalimentación). Para información sobre **definir** reglas de validación y crear validadores personalizados, consulta la guía de Validación (próximamente).
-### Checking validity
+### Verificando validez
-Use `valid()` and `invalid()` to check validation status:
+Usa `valid()` e `invalid()` para verificar el estado de validación:
```angular-ts
@Component({
@@ -98,26 +98,26 @@ export class Login {
}
```
-| Signal | Returns `true` when |
-| ----------- | --------------------------------------------------------------- |
-| `valid()` | Field passes all validation rules and has no pending validators |
-| `invalid()` | Field has validation errors |
+| Signal | Retorna `true` cuando |
+| ----------- | ----------------------------------------------------------------------------- |
+| `valid()` | El campo pasa todas las reglas de validación y no tiene validadores pendientes |
+| `invalid()` | El campo tiene errores de validación |
-When checking validity in code, use `invalid()` instead of `!valid()` if you want to distinguish between "has errors" and "validation pending." The reason for this is that both `valid()` and `invalid()` can be `false` simultaneously when async validation is pending because the field isn't valid yet since validation not complete and is also isn't invalid since no errors have been found yet.
+Al verificar validez en código, usa `invalid()` en lugar de `!valid()` si quieres distinguir entre "tiene errores" y "validación pendiente". La razón de esto es que tanto `valid()` como `invalid()` pueden ser `false` simultáneamente cuando la validación asíncrona está pendiente porque el campo no es válido aún ya que la validación no está completa y tampoco es inválido ya que no se han encontrado errores todavía.
-### Reading validation errors
+### Leyendo errores de validación
-Access the array of validation errors with `errors()`. Each error object contains:
+Accede al array de errores de validación con `errors()`. Cada objeto de error contiene:
-| Property | Description |
-| --------- | --------------------------------------------------------------- |
-| `kind` | The validation rule that failed (such as "required" or "email") |
-| `message` | Optional human-readable error message |
-| `field` | Reference to the `FieldTree` where the error occurred |
+| Propiedad | Descripción |
+| --------- | ------------------------------------------------------------------------------ |
+| `kind` | La regla de validación que falló (como "required" o "email") |
+| `message` | Mensaje de error legible opcional |
+| `field` | Referencia al `FieldTree` donde ocurrió el error |
-NOTE: The `message` property is optional. Validators can provide custom error messages, but if not specified, you may need to map error `kind` values to your own messages.
+NOTA: La propiedad `message` es opcional. Los validadores pueden proporcionar mensajes de error personalizados, pero si no se especifica, puede que necesites mapear los valores de `kind` de error a tus propios mensajes.
-Here's an example of how to display errors in your template:
+Aquí hay un ejemplo de cómo mostrar errores en tu plantilla:
```angular-ts
@Component({
@@ -135,11 +135,11 @@ Here's an example of how to display errors in your template:
})
```
-This approach loops through all errors for a field, displaying each error message to the user.
+Este enfoque recorre todos los errores de un campo, mostrando cada mensaje de error al usuario.
-### Pending validation
+### Validación pendiente
-The `pending()` signal indicates async validation is in progress:
+El signal `pending()` indica que la validación asíncrona está en progreso:
```angular-ts
@Component({
@@ -157,21 +157,21 @@ The `pending()` signal indicates async validation is in progress:
})
```
-This signal enables you to show loading states while async validation executes.
+Este signal te permite mostrar estados de carga mientras se ejecuta la validación asíncrona.
-## Interaction state
+## Estado de interacción
-Interaction state tracks whether users have interacted with fields, enabling patterns like "show errors only after the user has touched a field."
+El estado de interacción rastrea si los usuarios han interactuado con los campos, habilitando patrones como "mostrar errores solo después de que el usuario haya tocado un campo".
-### Touched state
+### Estado touched
-The `touched()` signal tracks whether a user has focused and then blurred a field. It becomes `true` when a user focuses and then blurs a field through user interaction (not programmatically). Hidden, disabled, and readonly fields are non-interactive and don't become touched from user interactions.
+El signal `touched()` rastrea si un usuario ha enfocado y luego desenfocado un campo. Se vuelve `true` cuando un usuario enfoca y luego desenfoca un campo a través de interacción del usuario (no programáticamente). Los campos ocultos, deshabilitados y de solo lectura no son interactivos y no se vuelven touched desde interacciones del usuario.
-### Dirty state
+### Estado dirty
-Forms often need to detect whether data has actually changed - for example, to warn users about unsaved changes or to enable a save button only when necessary. The `dirty()` signal tracks whether the user has modified the field.
+Los formularios a menudo necesitan detectar si los datos realmente han cambiado - por ejemplo, para advertir a los usuarios sobre cambios no guardados o para habilitar un botón de guardar solo cuando sea necesario. El signal `dirty()` rastrea si el usuario ha modificado el campo.
-The `dirty()` signal becomes `true` when the user modifies an interactive field's value, and remains `true` even if the value is changed back to match the initial value:
+El signal `dirty()` se vuelve `true` cuando el usuario modifica el valor de un campo interactivo, y permanece `true` incluso si el valor se cambia de vuelta para coincidir con el valor inicial:
```angular-ts
@Component({
@@ -192,33 +192,33 @@ export class Profile {
}
```
-Use `dirty()` for "unsaved changes" warnings or to enable save buttons only when data has changed.
+Usa `dirty()` para advertencias de "cambios no guardados" o para habilitar botones de guardar solo cuando los datos hayan cambiado.
### Touched vs dirty
-These signals track different user interactions:
+Estos signals rastrean diferentes interacciones del usuario:
-| Signal | When it becomes true |
-| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
-| `touched()` | User has focused and blurred an interactive field (even if they didn't change anything) |
-| `dirty()` | User has modified an interactive field (even if they never blurred it, and even if the current value matches the initial value) |
+| Signal | Cuándo se vuelve true |
+| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
+| `touched()` | El usuario ha enfocado y desenfocado un campo interactivo (incluso si no cambiaron nada) |
+| `dirty()` | El usuario ha modificado un campo interactivo (incluso si nunca lo desenfocaron, e incluso si el valor actual coincide con el inicial) |
-A field can be in different combinations:
+Un campo puede estar en diferentes combinaciones:
-| State | Scenario |
-| ---------------------- | --------------------------------------------------------- |
-| Touched but not dirty | User focused and blurred the field but made no changes |
-| Both touched and dirty | User focused the field, changed the value, and blurred it |
+| Estado | Escenario |
+| ------------------------ | ------------------------------------------------------------------- |
+| Touched pero no dirty | El usuario enfocó y desenfocó el campo pero no hizo cambios |
+| Ambos touched y dirty | El usuario enfocó el campo, cambió el valor y lo desenfocó |
-NOTE: Hidden, disabled, and readonly fields are non-interactive - they don't become touched or dirty from user interactions.
+NOTA: Los campos ocultos, deshabilitados y de solo lectura no son interactivos - no se vuelven touched o dirty desde interacciones del usuario.
-## Availability state
+## Estado de disponibilidad
-Availability state signals control whether fields are interactive, editable, or visible. Disabled, hidden, and readonly fields are non-interactive. They don't affect whether their parent form is valid, touched, or dirty.
+Los signals de estado de disponibilidad controlan si los campos son interactivos, editables o visibles. Los campos deshabilitados, ocultos y de solo lectura no son interactivos. No afectan si su formulario padre es válido, touched o dirty.
-### Disabled fields
+### Campos deshabilitados
-The `disabled()` signal indicates whether a field accepts user input. Disabled fields appear in the UI but users cannot interact with them.
+El signal `disabled()` indica si un campo acepta entrada del usuario. Los campos deshabilitados aparecen en la interfaz de usuario pero los usuarios no pueden interactuar con ellos.
```angular-ts
import { Component, signal } from '@angular/core'
@@ -248,20 +248,20 @@ export class Order {
}
```
-In this example, we use `valueOf(schemaPath.total)` to check the value of the `total` field to determine whether `couponCode` should be disabled.
+En este ejemplo, usamos `valueOf(schemaPath.total)` para verificar el valor del campo `total` para determinar si `couponCode` debe estar deshabilitado.
-NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+NOTA: El parámetro de callback de esquema (`schemaPath` en estos ejemplos) es un objeto `SchemaPathTree` que proporciona rutas a todos los campos en tu formulario. Puedes nombrar este parámetro como desees.
-When defining rules like `disabled()`, `hidden()`, or `readonly()`, the logic callback receives a `FieldContext` object that is typically destructured (such as `({valueOf})`). Two methods commonly used in validation rules are:
+Al definir reglas como `disabled()`, `hidden()` o `readonly()`, el callback de lógica recibe un objeto `FieldContext` que típicamente se desestructura (como `({valueOf})`). Dos métodos comúnmente usados en reglas de validación son:
-- `valueOf(schemaPath.otherField)` - Read the value of another field in the form
-- `value()` - A signal containing the value of the field the rule is applied to
+- `valueOf(schemaPath.otherField)` - Lee el valor de otro campo en el formulario
+- `value()` - Un signal que contiene el valor del campo al que se aplica la regla
-Disabled fields don't contribute to the parent form's validation state. Even if a disabled field would be invalid, the parent form can still be valid. The `disabled()` state affects interactivity and validation, but does not change the field's value.
+Los campos deshabilitados no contribuyen al estado de validación del formulario padre. Incluso si un campo deshabilitado sería inválido, el formulario padre aún puede ser válido. El estado `disabled()` afecta la interactividad y validación, pero no cambia el valor del campo.
-### Hidden fields
+### Campos ocultos
-The `hidden()` signal indicates whether a field is conditionally hidden. Use `hidden()` with `@if` to show or hide fields based on conditions:
+El signal `hidden()` indica si un campo está condicionalmente oculto. Usa `hidden()` con `@if` para mostrar u ocultar campos según condiciones:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -296,11 +296,11 @@ export class Profile {
}
```
-Hidden fields don't participate in validation. If a required field is hidden, it won't prevent form submission. The `hidden()` state affects availability and validation, but does not change the field's value.
+Los campos ocultos no participan en la validación. Si un campo requerido está oculto, no impedirá el envío del formulario. El estado `hidden()` afecta la disponibilidad y validación, pero no cambia el valor del campo.
-### Readonly fields
+### Campos de solo lectura
-The `readonly()` signal indicates whether a field is readonly. Readonly fields display their value but users cannot edit them:
+El signal `readonly()` indica si un campo es de solo lectura. Los campos de solo lectura muestran su valor pero los usuarios no pueden editarlos:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -333,23 +333,23 @@ export class Account {
}
```
-NOTE: The `[field]` directive automatically binds the `readonly` attribute based on the field's `readonly()` state, so you don't need to manually add `[readonly]="field().readonly()"`.
+NOTA: La directiva `[field]` vincula automáticamente el atributo `readonly` basándose en el estado `readonly()` del campo, por lo que no necesitas agregar manualmente `[readonly]="field().readonly()"`.
-Like disabled and hidden fields, readonly fields are non-interactive and don't affect parent form state. The `readonly()` state affects editability and validation, but does not change the field's value.
+Al igual que los campos deshabilitados y ocultos, los campos de solo lectura no son interactivos y no afectan el estado del formulario padre. El estado `readonly()` afecta la editabilidad y validación, pero no cambia el valor del campo.
-### When to use each
+### Cuándo usar cada uno
-| State | Use when | User can see it | User can interact | Contributes to validation |
-| ------------ | ------------------------------------------------------------------- | --------------- | ----------------- | ------------------------- |
-| `disabled()` | Field temporarily unavailable (such as based on other field values) | Yes | No | No |
-| `hidden()` | Field not relevant in current context | No (with @if) | No | No |
-| `readonly()` | Value should be visible but not editable | Yes | No | No |
+| Estado | Usar cuando | Usuario puede verlo | Usuario puede interactuar | Contribuye a la validación |
+| ------------ | --------------------------------------------------------------------------------- | ------------------- | ------------------------- | -------------------------- |
+| `disabled()` | El campo está temporalmente no disponible (como basándose en otros valores de campos) | Sí | No | No |
+| `hidden()` | El campo no es relevante en el contexto actual | No (con @if) | No | No |
+| `readonly()` | El valor debe ser visible pero no editable | Sí | No | No |
-## Form-level state
+## Estado a nivel de formulario
-The root form is also a field in the field tree. When you call it as a function, it also returns a `FieldState` object that aggregates the state of all child fields.
+El formulario raíz también es un campo en el field tree. Cuando lo llamas como una función, también retorna un objeto `FieldState` que agrega el estado de todos los campos hijos.
-### Accessing form state
+### Accediendo al estado del formulario
```angular-ts
@Component({
@@ -368,43 +368,43 @@ export class Login {
}
```
-In this example, the form is valid only when all child fields are valid. This allows you to enable/disable submit buttons based on overall form validity.
+En este ejemplo, el formulario es válido solo cuando todos los campos hijos son válidos. Esto te permite habilitar/deshabilitar botones de envío basándote en la validez general del formulario.
-### Form-level signals
+### Signals a nivel de formulario
-Because the root form is a field, it has the same signals (such as `valid()`, `invalid()`, `touched()`, `dirty()`, etc.).
+Debido a que el formulario raíz es un campo, tiene los mismos signals (como `valid()`, `invalid()`, `touched()`, `dirty()`, etc.).
-| Signal | Form-level behavior |
-| ----------- | -------------------------------------------------------------- |
-| `valid()` | All interactive fields are valid and no validators are pending |
-| `invalid()` | At least one interactive field has validation errors |
-| `pending()` | At least one interactive field has pending async validation |
-| `touched()` | User has touched at least one interactive field |
-| `dirty()` | User has modified at least one interactive field |
+| Signal | Comportamiento a nivel de formulario |
+| ----------- | -------------------------------------------------------------------------------- |
+| `valid()` | Todos los campos interactivos son válidos y no hay validadores pendientes |
+| `invalid()` | Al menos un campo interactivo tiene errores de validación |
+| `pending()` | Al menos un campo interactivo tiene validación asíncrona pendiente |
+| `touched()` | El usuario ha tocado al menos un campo interactivo |
+| `dirty()` | El usuario ha modificado al menos un campo interactivo |
-### When to use form-level vs field-level
+### Cuándo usar a nivel de formulario vs a nivel de campo
-**Use form-level state for:**
+**Usa estado a nivel de formulario para:**
-- Submit button enabled/disabled state
-- "Save" button state
-- Overall form validity checks
-- Unsaved changes warnings
+- Estado habilitado/deshabilitado del botón de envío
+- Estado del botón "Guardar"
+- Verificaciones generales de validez del formulario
+- Advertencias de cambios no guardados
-**Use field-level state for:**
+**Usa estado a nivel de campo para:**
-- Individual field error messages
-- Field-specific styling
-- Per-field validation feedback
-- Conditional field availability
+- Mensajes de error de campos individuales
+- Estilo específico del campo
+- Retroalimentación de validación por campo
+- Disponibilidad condicional de campos
-## State propagation
+## Propagación de estado
-Field state propagates from child fields up through parent field groups to the root form.
+El estado del campo se propaga desde los campos hijos hacia arriba a través de grupos de campos padre hasta el formulario raíz.
-### How child state affects parent forms
+### Cómo el estado hijo afecta a los formularios padre
-When a child field becomes invalid, its parent field group becomes invalid, and so does the root form. When a child becomes touched or dirty, the parent field group and root form reflect that change. This aggregation allows you to check validity at any level - field or entire form.
+Cuando un campo hijo se vuelve inválido, su grupo de campos padre se vuelve inválido, y también lo hace el formulario raíz. Cuando un hijo se vuelve touched o dirty, el grupo de campos padre y el formulario raíz reflejan ese cambio. Esta agregación te permite verificar la validez en cualquier nivel - campo o formulario completo.
```ts
const userModel = signal({
@@ -426,9 +426,9 @@ userForm.profile.firstName().invalid() === true
// → userForm().invalid() === true
```
-### Hidden, disabled, and readonly fields
+### Campos ocultos, deshabilitados y de solo lectura
-Hidden, disabled, and readonly fields are non-interactive and don't affect parent form state:
+Los campos ocultos, deshabilitados y de solo lectura no son interactivos y no afectan el estado del formulario padre:
```ts
const orderModel = signal({
@@ -442,17 +442,17 @@ const orderForm = form(orderModel, schemaPath => {
})
```
-In this example, when `shippingAddress` is hidden, it doesn't affect form validity. As a result, even if `shippingAddress` is empty and required, the form can be valid.
+En este ejemplo, cuando `shippingAddress` está oculto, no afecta la validez del formulario. Como resultado, incluso si `shippingAddress` está vacío y es requerido, el formulario puede ser válido.
-This behavior prevents hidden, disabled, or readonly fields from blocking form submission or affecting validation, touched, and dirty state.
+Este comportamiento evita que los campos ocultos, deshabilitados o de solo lectura bloqueen el envío del formulario o afecten el estado de validación, touched y dirty.
-## Using state in templates
+## Usando estado en plantillas
-Field state signals integrate seamlessly with Angular templates, enabling reactive form user experiences without manual event handling.
+Los signals de estado de campo se integran perfectamente con las plantillas de Angular, habilitando experiencias de usuario de formularios reactivos sin manejo manual de eventos.
-### Conditional error display
+### Visualización condicional de errores
-Show errors only after a user has interacted with a field:
+Muestra errores solo después de que un usuario haya interactuado con un campo:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -481,11 +481,11 @@ export class Signup {
}
```
-This pattern prevents showing errors before users have had a chance to interact with the field. Errors appear only after the user has focused and then left the field.
+Este patrón evita mostrar errores antes de que los usuarios hayan tenido la oportunidad de interactuar con el campo. Los errores aparecen solo después de que el usuario haya enfocado y luego salido del campo.
-### Conditional field availability
+### Disponibilidad condicional de campos
-Use the `hidden()` signal with `@if` to show or hide fields conditionally:
+Usa el signal `hidden()` con `@if` para mostrar u ocultar campos condicionalmente:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -520,15 +520,15 @@ export class Order {
}
```
-Hidden fields don't participate in validation, allowing the form to be submitted even if the hidden field would otherwise be invalid.
+Los campos ocultos no participan en la validación, permitiendo que el formulario se envíe incluso si el campo oculto sería inválido de lo contrario.
-## Using field state in component logic
+## Usando estado de campo en lógica de componentes
-Field state signals work with Angular's reactive primitives like `computed()` and `effect()` for advanced form logic.
+Los signals de estado de campo funcionan con las primitivas reactivas de Angular como `computed()` y `effect()` para lógica de formularios avanzada.
-### Validation checks before submission
+### Verificaciones de validación antes del envío
-Check form validity in component methods:
+Verifica la validez del formulario en métodos del componente:
```ts
export class Registration {
@@ -559,11 +559,11 @@ export class Registration {
}
```
-This ensures only valid, fully-validated data reaches your API.
+Esto asegura que solo datos válidos y completamente validados lleguen a tu API.
-### Derived state with computed
+### Estado derivado con computed
-Create computed signals based on field state to automatically update when the underlying field state changes:
+Crea signals computed basados en el estado del campo para actualizarse automáticamente cuando el estado del campo subyacente cambia:
```ts
export class Password {
@@ -588,13 +588,13 @@ export class Password {
}
```
-### Programmatic state changes
+### Cambios de estado programáticos
-While field state typically updates through user interactions (typing, focusing, blurring), you sometimes need to control it programmatically. Common scenarios include form submission and resetting forms.
+Aunque el estado del campo típicamente se actualiza a través de interacciones del usuario (escribir, enfocar, desenfocar), a veces necesitas controlarlo programáticamente. Los escenarios comunes incluyen el envío de formularios y el reseteo de formularios.
-#### Form submission
+#### Envío de formularios
-When a user submits a form, use the `submit()` function to handle validation and reveal errors:
+Cuando un usuario envía un formulario, usa la función `submit()` para manejar la validación y revelar errores:
```ts
import { Component, signal } from '@angular/core'
@@ -621,11 +621,11 @@ export class Registration {
}
```
-The `submit()` function automatically marks all fields as touched (revealing validation errors) and only executes your callback if the form is valid.
+La función `submit()` marca automáticamente todos los campos como touched (revelando errores de validación) y solo ejecuta tu callback si el formulario es válido.
-#### Resetting forms after submission
+#### Reseteando formularios después del envío
-After successfully submitting a form, you may want to return it to its initial state - clearing both user interaction history and field values. The `reset()` method clears the touched and dirty flags but doesn't change field values, so you need to update your model separately:
+Después de enviar exitosamente un formulario, puedes querer devolverlo a su estado inicial - limpiando tanto el historial de interacción del usuario como los valores de los campos. El método `reset()` limpia las banderas touched y dirty pero no cambia los valores de los campos, por lo que necesitas actualizar tu modelo por separado:
```ts
export class Contact {
@@ -646,11 +646,11 @@ export class Contact {
}
```
-This two-step reset ensures the form is ready for new input without showing stale error messages or dirty state indicators.
+Este reseteo de dos pasos asegura que el formulario esté listo para nueva entrada sin mostrar mensajes de error obsoletos o indicadores de estado dirty.
-## Styling based on validation state
+## Estilizando basándose en el estado de validación
-You can apply custom styles to your form by binding CSS classes based on the validation state:
+Puedes aplicar estilos personalizados a tu formulario vinculando clases CSS basándote en el estado de validación:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -685,11 +685,11 @@ export class StyleExample {
}
```
-Checking both `touched()` and validation state ensures styles only appear after the user has interacted with the field.
+Verificar tanto `touched()` como el estado de validación asegura que los estilos solo aparezcan después de que el usuario haya interactuado con el campo.
-## Next steps
+## Próximos pasos
-Here are other related guides on Signal Forms:
+Aquí hay otras guías relacionadas sobre Signal Forms:
-- [Form Models guide](guide/forms/signals/models) - Creating models and updating values
-- Validation guide - Defining validation rules and custom validators (coming soon)
+- [Guía de modelos de formularios](guide/forms/signals/models) - Creando modelos y actualizando valores
+- Guía de validación - Definiendo reglas de validación y validadores personalizados (próximamente)
diff --git a/adev-es/src/content/guide/forms/signals/models.en.md b/adev-es/src/content/guide/forms/signals/models.en.md
new file mode 100644
index 0000000..64cd1da
--- /dev/null
+++ b/adev-es/src/content/guide/forms/signals/models.en.md
@@ -0,0 +1,536 @@
+# Form models
+
+Form models are the foundation of Signal Forms, serving as the single source of truth for your form data. This guide explores how to create form models, update them, and design them for maintainability.
+
+NOTE: Form models are distinct from Angular's `model()` signal used for component two-way binding. A form model is a writable signal that stores form data, while `model()` creates inputs/outputs for parent/child component communication.
+
+## What form models solve
+
+Forms require managing data that changes over time. Without a clear structure, this data can become scattered across component properties, making it difficult to track changes, validate input, or submit data to a server.
+
+Form models solve this by centralizing form data in a single writable signal. When the model updates, the form automatically reflects those changes. When users interact with the form, the model updates accordingly.
+
+## Creating models
+
+A form model is a writable signal created with Angular's `signal()` function. The signal holds an object that represents your form's data structure.
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-login',
+ imports: [Field],
+ template: `
+
+
+ `
+})
+export class LoginComponent {
+ loginModel = signal({
+ email: '',
+ password: ''
+ })
+
+ loginForm = form(this.loginModel)
+}
+```
+
+The `form()` function accepts the model signal and creates a **field tree** - a special object structure that mirrors your model's shape. The field tree is both navigable (access child fields with dot notation like `loginForm.email`) and callable (call a field as a function to access its state).
+
+The `[field]` directive binds each input element to its corresponding field in the field tree, enabling automatic two-way synchronization between the UI and model.
+
+### Using TypeScript types
+
+While TypeScript infers types from object literals, defining explicit types improves code quality and provides better IntelliSense support.
+
+```ts
+interface LoginData {
+ email: string
+ password: string
+}
+
+export class LoginComponent {
+ loginModel = signal({
+ email: '',
+ password: ''
+ })
+
+ loginForm = form(this.loginModel)
+}
+```
+
+With explicit types, the field tree provides full type safety. Accessing `loginForm.email` is typed as `FieldTree`, and attempting to access a non-existent property results in a compile-time error.
+
+```ts
+// TypeScript knows this is FieldTree
+const emailField = loginForm.email
+
+// TypeScript error: Property 'username' does not exist
+const usernameField = loginForm.username
+```
+
+### Initializing all fields
+
+Form models should provide initial values for all fields you want to include in the field tree.
+
+```ts
+// Good: All fields initialized
+const userModel = signal({
+ name: '',
+ email: '',
+ age: 0
+})
+
+// Avoid: Missing initial value
+const userModel = signal({
+ name: '',
+ email: ''
+ // age field is not defined - cannot access userForm.age
+})
+```
+
+For optional fields, explicitly set them to `null` or an empty value:
+
+```ts
+interface UserData {
+ name: string
+ email: string
+ phoneNumber: string | null
+}
+
+const userModel = signal({
+ name: '',
+ email: '',
+ phoneNumber: null
+})
+```
+
+Fields set to `undefined` are excluded from the field tree. A model with `{value: undefined}` behaves identically to `{}` - accessing the field returns `undefined` rather than a `FieldTree`.
+
+### Dynamic field addition
+
+You can dynamically add fields by updating the model with new properties. The field tree automatically updates to include new fields when they appear in the model value.
+
+```ts
+// Start with just email
+const model = signal({ email: '' })
+const myForm = form(model)
+
+// Later, add a password field
+model.update(current => ({ ...current, password: '' }))
+// myForm.password is now available
+```
+
+This pattern is useful when fields become relevant based on user choices or loaded data.
+
+## Reading model values
+
+You can access form values in two ways: directly from the model signal, or through individual fields. Each approach serves a different purpose.
+
+### Reading from the model
+
+Access the model signal when you need the complete form data, such as during form submission:
+
+```ts
+onSubmit() {
+ const formData = this.loginModel();
+ console.log(formData.email, formData.password);
+
+ // Send to server
+ await this.authService.login(formData);
+}
+```
+
+The model signal returns the entire data object, making it ideal for operations that work with the complete form state.
+
+### Reading from field state
+
+Each field in the field tree is a function. Calling a field returns a `FieldState` object containing reactive signals for the field's value, validation status, and interaction state.
+
+Access field state when working with individual fields in templates or reactive computations:
+
+```angular-ts
+@Component({
+ template: `
+
Current email: {{ loginForm.email().value() }}
+
Password length: {{ passwordLength() }}
+ `
+})
+export class LoginComponent {
+ loginModel = signal({ email: '', password: '' })
+ loginForm = form(this.loginModel)
+
+ passwordLength = computed(() => {
+ return this.loginForm.password().value().length
+ })
+}
+```
+
+Field state provides reactive signals for each field's value, making it suitable for displaying field-specific information or creating derived state.
+
+TIP: Field state includes many more signals beyond `value()`, such as validation state (e.g., valid, invalid, errors), interaction tracking (e.g., touched, dirty), and visibility (e.g., hidden, disabled).
+
+
+
+
+## Updating form models programmatically
+
+Form models update through programmatic mechanisms:
+
+1. [Replace the entire form model](#replacing-form-models-with-set) with `set()`
+2. [Update one or more fields](#update-one-or-more-fields-with-update) with `update()`
+3. [Update a single field directly](#update-a-single-field-directly-with-set) through field state
+
+### Replacing form models with `set()`
+
+Use `set()` on the form model to replace the entire value:
+
+```ts
+loadUserData() {
+ this.userModel.set({
+ name: 'Alice',
+ email: 'alice@example.com',
+ age: 30,
+ });
+}
+
+resetForm() {
+ this.userModel.set({
+ name: '',
+ email: '',
+ age: 0,
+ });
+}
+```
+
+This approach works well when loading data from an API or resetting the entire form.
+
+### Update one or more fields with `update()`
+
+Use `update()` to modify specific fields while preserving others:
+
+```ts
+updateEmail(newEmail: string) {
+ this.userModel.update(current => ({
+ ...current,
+ email: newEmail,
+ }));
+}
+```
+
+This pattern is useful when you need to change one or more fields based on the current model state.
+
+### Update a single field directly with `set()`
+
+Use `set()` on individual field values to directly update the field state:
+
+```ts
+clearEmail() {
+ this.userForm.email().value.set('');
+}
+
+incrementAge() {
+ const currentAge = this.userForm.age().value();
+ this.userForm.age().value.set(currentAge + 1);
+}
+```
+
+These are also known as "field-level updates." They automatically propagate to the model signal and keep both in sync.
+
+### Example: Loading data from an API
+
+A common pattern involves fetching data and populating the model:
+
+```ts
+export class UserProfileComponent {
+ userModel = signal({
+ name: '',
+ email: '',
+ bio: ''
+ })
+
+ userForm = form(this.userModel)
+ private userService = inject(UserService)
+
+ ngOnInit() {
+ this.loadUserProfile()
+ }
+
+ async loadUserProfile() {
+ const userData = await this.userService.getUserProfile()
+ this.userModel.set(userData)
+ }
+}
+```
+
+The form fields automatically update when the model changes, displaying the fetched data without additional code.
+
+## Two-way data binding
+
+The `[field]` directive creates automatic two-way synchronization between the model, form state, and UI.
+
+### How data flows
+
+Changes flow bidirectionally:
+
+**User input → Model:**
+
+1. User types in an input element
+2. The `[field]` directive detects the change
+3. Field state updates
+4. Model signal updates
+
+**Programmatic update → UI:**
+
+1. Code updates the model with `set()` or `update()`
+2. Model signal notifies subscribers
+3. Field state updates
+4. The `[field]` directive updates the input element
+
+This synchronization happens automatically. You don't write subscriptions or event handlers to keep the model and UI in sync.
+
+### Example: Both directions
+
+```angular-ts
+@Component({
+ template: `
+
+
+
Current name: {{ userModel().name }}
+ `
+})
+export class UserComponent {
+ userModel = signal({ name: '' })
+ userForm = form(this.userModel)
+
+ setName(name: string) {
+ this.userModel.update(current => ({ ...current, name }))
+ // Input automatically displays 'Bob'
+ }
+}
+```
+
+When the user types in the input, `userModel().name` updates. When the button is clicked, the input value changes to "Bob". No manual synchronization code is required.
+
+## Model structure patterns
+
+Form models can be flat objects or contain nested objects and arrays. The structure you choose affects how you access fields and organize validation.
+
+### Flat vs nested models
+
+Flat form models keep all fields at the top level:
+
+```ts
+// Flat structure
+const userModel = signal({
+ name: '',
+ email: '',
+ street: '',
+ city: '',
+ state: '',
+ zip: ''
+})
+```
+
+Nested models group related fields:
+
+```ts
+// Nested structure
+const userModel = signal({
+ name: '',
+ email: '',
+ address: {
+ street: '',
+ city: '',
+ state: '',
+ zip: ''
+ }
+})
+```
+
+**Use flat structures when:**
+
+- Fields don't have clear conceptual groupings
+- You want simpler field access (`userForm.city` vs `userForm.address.city`)
+- Validation rules span multiple potential groups
+
+**Use nested structures when:**
+
+- Fields form a clear conceptual group (like an address)
+- The grouped data matches your API structure
+- You want to validate the group as a unit
+
+### Working with nested objects
+
+You can access nested fields by following the object path:
+
+```ts
+const userModel = signal({
+ profile: {
+ firstName: '',
+ lastName: ''
+ },
+ settings: {
+ theme: 'light',
+ notifications: true
+ }
+})
+
+const userForm = form(userModel)
+
+// Access nested fields
+userForm.profile.firstName // FieldTree
+userForm.settings.theme // FieldTree
+```
+
+In templates, you bind nested fields the same way as top-level fields:
+
+```angular-ts
+@Component({
+ template: `
+
+
+
+
+ `,
+})
+```
+
+### Working with arrays
+
+Models can include arrays for collections of items:
+
+```ts
+const orderModel = signal({
+ customerName: '',
+ items: [{ product: '', quantity: 0, price: 0 }]
+})
+
+const orderForm = form(orderModel)
+
+// Access array items by index
+orderForm.items[0].product // FieldTree
+orderForm.items[0].quantity // FieldTree
+```
+
+Array items containing objects automatically receive tracking identities, which helps maintain field state even when items change position in the array. This ensures validation state and user interactions persist correctly when arrays are reordered.
+
+
+
+## Model design best practices
+
+Well-designed form models make forms easier to maintain and extend. Follow these patterns when designing your models.
+
+### Use specific types
+
+Always define interfaces or types for your models as shown in [Using TypeScript types](#using-typescript-types). Explicit types provide better IntelliSense, catch errors at compile time, and serve as documentation for what data the form contains.
+
+### Initialize all fields
+
+Provide initial values for every field in your model:
+
+```ts
+// Good: All fields initialized
+const taskModel = signal({
+ title: '',
+ description: '',
+ priority: 'medium',
+ completed: false
+})
+```
+
+```ts
+// Avoid: Partial initialization
+const taskModel = signal({
+ title: ''
+ // Missing description, priority, completed
+})
+```
+
+Missing initial values mean those fields won't exist in the field tree, making them inaccessible for form interactions.
+
+### Keep models focused
+
+Each model should represent a single form or a cohesive set of related data:
+
+```ts
+// Good: Focused on login
+const loginModel = signal({
+ email: '',
+ password: ''
+})
+```
+
+```ts
+// Avoid: Mixing unrelated concerns
+const appModel = signal({
+ // Login data
+ email: '',
+ password: '',
+ // User preferences
+ theme: 'light',
+ language: 'en',
+ // Shopping cart
+ cartItems: []
+})
+```
+
+Separate models for different concerns makes forms easier to understand and reuse. Create multiple forms if you're managing distinct sets of data.
+
+### Consider validation requirements
+
+Design models with validation in mind. Group fields that validate together:
+
+```ts
+// Good: Password fields grouped for comparison
+interface PasswordChangeData {
+ currentPassword: string
+ newPassword: string
+ confirmPassword: string
+}
+```
+
+This structure makes cross-field validation (like checking if `newPassword` matches `confirmPassword`) more natural.
+
+### Plan for initial state
+
+Consider whether your form starts empty or pre-populated:
+
+```ts
+// Form that starts empty (new user)
+const newUserModel = signal({
+ name: '',
+ email: '',
+});
+
+// Form that loads existing data
+const editUserModel = signal({
+ name: '',
+ email: '',
+});
+
+// Later, in ngOnInit:
+ngOnInit() {
+ this.loadExistingUser();
+}
+
+async loadExistingUser() {
+ const user = await this.userService.getUser(this.userId);
+ this.editUserModel.set(user);
+}
+```
+
+For forms that always start with existing data, you might wait to render the form until data loads in order to avoid a flash of empty fields.
+
+
+
diff --git a/adev-es/src/content/guide/forms/signals/models.md b/adev-es/src/content/guide/forms/signals/models.md
index 64cd1da..69073e1 100644
--- a/adev-es/src/content/guide/forms/signals/models.md
+++ b/adev-es/src/content/guide/forms/signals/models.md
@@ -1,18 +1,18 @@
-# Form models
+# Modelos de formularios
-Form models are the foundation of Signal Forms, serving as the single source of truth for your form data. This guide explores how to create form models, update them, and design them for maintainability.
+Los modelos de formularios son la base de Signal Forms, sirviendo como la única fuente de verdad para los datos de tu formulario. Esta guía explora cómo crear modelos de formularios, actualizarlos y diseñarlos para mantenerlos fácilmente.
-NOTE: Form models are distinct from Angular's `model()` signal used for component two-way binding. A form model is a writable signal that stores form data, while `model()` creates inputs/outputs for parent/child component communication.
+NOTA: Los modelos de formularios son distintos del signal `model()` de Angular usado para enlace bidireccional de componentes. Un modelo de formulario es un signal editable que almacena datos de formularios, mientras que `model()` crea inputs/outputs para comunicación entre componentes padre/hijo.
-## What form models solve
+## Qué resuelven los modelos de formularios
-Forms require managing data that changes over time. Without a clear structure, this data can become scattered across component properties, making it difficult to track changes, validate input, or submit data to a server.
+Los formularios requieren gestionar datos que cambian con el tiempo. Sin una estructura clara, estos datos pueden dispersarse en propiedades del componente, dificultando el rastreo de cambios, la validación de entradas o el envío de datos a un servidor.
-Form models solve this by centralizing form data in a single writable signal. When the model updates, the form automatically reflects those changes. When users interact with the form, the model updates accordingly.
+Los modelos de formularios resuelven esto centralizando los datos del formulario en un único signal editable. Cuando el modelo se actualiza, el formulario refleja automáticamente esos cambios. Cuando los usuarios interactúan con el formulario, el modelo se actualiza en consecuencia.
-## Creating models
+## Creando modelos
-A form model is a writable signal created with Angular's `signal()` function. The signal holds an object that represents your form's data structure.
+Un modelo de formulario es un signal editable creado con la función `signal()` de Angular. El signal contiene un objeto que representa la estructura de datos de tu formulario.
```angular-ts
import { Component, signal } from '@angular/core'
@@ -36,13 +36,13 @@ export class LoginComponent {
}
```
-The `form()` function accepts the model signal and creates a **field tree** - a special object structure that mirrors your model's shape. The field tree is both navigable (access child fields with dot notation like `loginForm.email`) and callable (call a field as a function to access its state).
+La función `form()` acepta el signal del modelo y crea un **field tree** - una estructura de objeto especial que refleja la forma de tu modelo. El field tree es tanto navegable (accede a campos hijos con notación de punto como `loginForm.email`) como invocable (llama a un campo como una función para acceder a su estado).
-The `[field]` directive binds each input element to its corresponding field in the field tree, enabling automatic two-way synchronization between the UI and model.
+La directiva `[field]` vincula cada elemento input a su campo correspondiente en el field tree, habilitando sincronización bidireccional automática entre la interfaz de usuario y el modelo.
-### Using TypeScript types
+### Usando tipos de TypeScript
-While TypeScript infers types from object literals, defining explicit types improves code quality and provides better IntelliSense support.
+Aunque TypeScript infiere tipos de literales de objetos, definir tipos explícitos mejora la calidad del código y proporciona mejor soporte de IntelliSense.
```ts
interface LoginData {
@@ -60,7 +60,7 @@ export class LoginComponent {
}
```
-With explicit types, the field tree provides full type safety. Accessing `loginForm.email` is typed as `FieldTree`, and attempting to access a non-existent property results in a compile-time error.
+Con tipos explícitos, el field tree proporciona seguridad de tipos completa. Acceder a `loginForm.email` está tipado como `FieldTree`, e intentar acceder a una propiedad inexistente resulta en un error de compilación.
```ts
// TypeScript knows this is FieldTree
@@ -70,9 +70,9 @@ const emailField = loginForm.email
const usernameField = loginForm.username
```
-### Initializing all fields
+### Inicializando todos los campos
-Form models should provide initial values for all fields you want to include in the field tree.
+Los modelos de formularios deben proporcionar valores iniciales para todos los campos que quieras incluir en el field tree.
```ts
// Good: All fields initialized
@@ -90,7 +90,7 @@ const userModel = signal({
})
```
-For optional fields, explicitly set them to `null` or an empty value:
+Para campos opcionales, establécelos explícitamente a `null` o un valor vacío:
```ts
interface UserData {
@@ -106,11 +106,11 @@ const userModel = signal({
})
```
-Fields set to `undefined` are excluded from the field tree. A model with `{value: undefined}` behaves identically to `{}` - accessing the field returns `undefined` rather than a `FieldTree`.
+Los campos establecidos a `undefined` se excluyen del field tree. Un modelo con `{value: undefined}` se comporta de manera idéntica a `{}` - acceder al campo retorna `undefined` en lugar de un `FieldTree`.
-### Dynamic field addition
+### Adición dinámica de campos
-You can dynamically add fields by updating the model with new properties. The field tree automatically updates to include new fields when they appear in the model value.
+Puedes agregar campos dinámicamente actualizando el modelo con nuevas propiedades. El field tree se actualiza automáticamente para incluir nuevos campos cuando aparecen en el valor del modelo.
```ts
// Start with just email
@@ -122,15 +122,15 @@ model.update(current => ({ ...current, password: '' }))
// myForm.password is now available
```
-This pattern is useful when fields become relevant based on user choices or loaded data.
+Este patrón es útil cuando los campos se vuelven relevantes según las elecciones del usuario o datos cargados.
-## Reading model values
+## Leyendo valores del modelo
-You can access form values in two ways: directly from the model signal, or through individual fields. Each approach serves a different purpose.
+Puedes acceder a los valores del formulario de dos maneras: directamente desde el signal del modelo, o a través de campos individuales. Cada enfoque sirve para un propósito diferente.
-### Reading from the model
+### Leyendo desde el modelo
-Access the model signal when you need the complete form data, such as during form submission:
+Accede al signal del modelo cuando necesites los datos completos del formulario, como durante el envío del formulario:
```ts
onSubmit() {
@@ -142,13 +142,13 @@ onSubmit() {
}
```
-The model signal returns the entire data object, making it ideal for operations that work with the complete form state.
+El signal del modelo retorna el objeto de datos completo, haciéndolo ideal para operaciones que trabajan con el estado completo del formulario.
-### Reading from field state
+### Leyendo desde el estado del campo
-Each field in the field tree is a function. Calling a field returns a `FieldState` object containing reactive signals for the field's value, validation status, and interaction state.
+Cada campo en el field tree es una función. Llamar a un campo retorna un objeto `FieldState` que contiene signals reactivos para el valor del campo, estado de validación y estado de interacción.
-Access field state when working with individual fields in templates or reactive computations:
+Accede al estado del campo cuando trabajes con campos individuales en plantillas o cálculos reactivos:
```angular-ts
@Component({
@@ -167,24 +167,24 @@ export class LoginComponent {
}
```
-Field state provides reactive signals for each field's value, making it suitable for displaying field-specific information or creating derived state.
+El estado del campo proporciona signals reactivos para el valor de cada campo, haciéndolo adecuado para mostrar información específica del campo o crear estado derivado.
-TIP: Field state includes many more signals beyond `value()`, such as validation state (e.g., valid, invalid, errors), interaction tracking (e.g., touched, dirty), and visibility (e.g., hidden, disabled).
+CONSEJO: El estado del campo incluye muchos más signals además de `value()`, como estado de validación (ej., valid, invalid, errors), rastreo de interacción (ej., touched, dirty) y visibilidad (ej., hidden, disabled).
-## Updating form models programmatically
+## Actualizando modelos de formularios programáticamente
-Form models update through programmatic mechanisms:
+Los modelos de formularios se actualizan a través de mecanismos programáticos:
-1. [Replace the entire form model](#replacing-form-models-with-set) with `set()`
-2. [Update one or more fields](#update-one-or-more-fields-with-update) with `update()`
-3. [Update a single field directly](#update-a-single-field-directly-with-set) through field state
+1. [Reemplazar todo el modelo de formulario](#reemplazando-modelos-de-formularios-con-set) con `set()`
+2. [Actualizar uno o más campos](#actualizar-uno-o-más-campos-con-update) con `update()`
+3. [Actualizar un solo campo directamente](#actualizar-un-solo-campo-directamente-con-set) a través del estado del campo
-### Replacing form models with `set()`
+### Reemplazando modelos de formularios con `set()`
-Use `set()` on the form model to replace the entire value:
+Usa `set()` en el modelo de formulario para reemplazar el valor completo:
```ts
loadUserData() {
@@ -204,11 +204,11 @@ resetForm() {
}
```
-This approach works well when loading data from an API or resetting the entire form.
+Este enfoque funciona bien cuando cargas datos desde una API o reseteas todo el formulario.
-### Update one or more fields with `update()`
+### Actualizar uno o más campos con `update()`
-Use `update()` to modify specific fields while preserving others:
+Usa `update()` para modificar campos específicos mientras preservas otros:
```ts
updateEmail(newEmail: string) {
@@ -219,11 +219,11 @@ updateEmail(newEmail: string) {
}
```
-This pattern is useful when you need to change one or more fields based on the current model state.
+Este patrón es útil cuando necesitas cambiar uno o más campos basándote en el estado actual del modelo.
-### Update a single field directly with `set()`
+### Actualizar un solo campo directamente con `set()`
-Use `set()` on individual field values to directly update the field state:
+Usa `set()` en valores de campos individuales para actualizar directamente el estado del campo:
```ts
clearEmail() {
@@ -236,11 +236,11 @@ incrementAge() {
}
```
-These are also known as "field-level updates." They automatically propagate to the model signal and keep both in sync.
+Estas también se conocen como "actualizaciones a nivel de campo". Se propagan automáticamente al signal del modelo y mantienen ambos sincronizados.
-### Example: Loading data from an API
+### Ejemplo: Cargando datos desde una API
-A common pattern involves fetching data and populating the model:
+Un patrón común implica obtener datos y poblar el modelo:
```ts
export class UserProfileComponent {
@@ -264,33 +264,33 @@ export class UserProfileComponent {
}
```
-The form fields automatically update when the model changes, displaying the fetched data without additional code.
+Los campos del formulario se actualizan automáticamente cuando el modelo cambia, mostrando los datos obtenidos sin código adicional.
-## Two-way data binding
+## Enlace bidireccional de datos
-The `[field]` directive creates automatic two-way synchronization between the model, form state, and UI.
+La directiva `[field]` crea sincronización bidireccional automática entre el modelo, el estado del formulario y la interfaz de usuario.
-### How data flows
+### Cómo fluyen los datos
-Changes flow bidirectionally:
+Los cambios fluyen bidireccionalmente:
-**User input → Model:**
+**Entrada del usuario → Modelo:**
-1. User types in an input element
-2. The `[field]` directive detects the change
-3. Field state updates
-4. Model signal updates
+1. El usuario escribe en un elemento input
+2. La directiva `[field]` detecta el cambio
+3. El estado del campo se actualiza
+4. El signal del modelo se actualiza
-**Programmatic update → UI:**
+**Actualización programática → UI:**
-1. Code updates the model with `set()` or `update()`
-2. Model signal notifies subscribers
-3. Field state updates
-4. The `[field]` directive updates the input element
+1. El código actualiza el modelo con `set()` o `update()`
+2. El signal del modelo notifica a los suscriptores
+3. El estado del campo se actualiza
+4. La directiva `[field]` actualiza el elemento input
-This synchronization happens automatically. You don't write subscriptions or event handlers to keep the model and UI in sync.
+Esta sincronización ocurre automáticamente. No escribes suscripciones o manejadores de eventos para mantener el modelo y la interfaz de usuario sincronizados.
-### Example: Both directions
+### Ejemplo: Ambas direcciones
```angular-ts
@Component({
@@ -311,15 +311,15 @@ export class UserComponent {
}
```
-When the user types in the input, `userModel().name` updates. When the button is clicked, the input value changes to "Bob". No manual synchronization code is required.
+Cuando el usuario escribe en el input, `userModel().name` se actualiza. Cuando se hace clic en el botón, el valor del input cambia a "Bob". No se requiere código de sincronización manual.
-## Model structure patterns
+## Patrones de estructura de modelo
-Form models can be flat objects or contain nested objects and arrays. The structure you choose affects how you access fields and organize validation.
+Los modelos de formularios pueden ser objetos planos o contener objetos anidados y arrays. La estructura que elijas afecta cómo accedes a los campos y organizas la validación.
-### Flat vs nested models
+### Modelos planos vs anidados
-Flat form models keep all fields at the top level:
+Los modelos de formularios planos mantienen todos los campos en el nivel superior:
```ts
// Flat structure
@@ -333,7 +333,7 @@ const userModel = signal({
})
```
-Nested models group related fields:
+Los modelos anidados agrupan campos relacionados:
```ts
// Nested structure
@@ -349,21 +349,21 @@ const userModel = signal({
})
```
-**Use flat structures when:**
+**Usa estructuras planas cuando:**
-- Fields don't have clear conceptual groupings
-- You want simpler field access (`userForm.city` vs `userForm.address.city`)
-- Validation rules span multiple potential groups
+- Los campos no tienen agrupaciones conceptuales claras
+- Quieres acceso más simple a campos (`userForm.city` vs `userForm.address.city`)
+- Las reglas de validación abarcan múltiples grupos potenciales
-**Use nested structures when:**
+**Usa estructuras anidadas cuando:**
-- Fields form a clear conceptual group (like an address)
-- The grouped data matches your API structure
-- You want to validate the group as a unit
+- Los campos forman un grupo conceptual claro (como una dirección)
+- Los datos agrupados coinciden con la estructura de tu API
+- Quieres validar el grupo como una unidad
-### Working with nested objects
+### Trabajando con objetos anidados
-You can access nested fields by following the object path:
+Puedes acceder a campos anidados siguiendo la ruta del objeto:
```ts
const userModel = signal({
@@ -384,7 +384,7 @@ userForm.profile.firstName // FieldTree
userForm.settings.theme // FieldTree
```
-In templates, you bind nested fields the same way as top-level fields:
+En plantillas, vinculas campos anidados de la misma manera que campos de nivel superior:
```angular-ts
@Component({
@@ -400,9 +400,9 @@ In templates, you bind nested fields the same way as top-level fields:
})
```
-### Working with arrays
+### Trabajando con arrays
-Models can include arrays for collections of items:
+Los modelos pueden incluir arrays para colecciones de elementos:
```ts
const orderModel = signal({
@@ -417,21 +417,21 @@ orderForm.items[0].product // FieldTree
orderForm.items[0].quantity // FieldTree
```
-Array items containing objects automatically receive tracking identities, which helps maintain field state even when items change position in the array. This ensures validation state and user interactions persist correctly when arrays are reordered.
+Los elementos de arrays que contienen objetos reciben automáticamente identidades de rastreo, lo que ayuda a mantener el estado del campo incluso cuando los elementos cambian de posición en el array. Esto asegura que el estado de validación y las interacciones del usuario persistan correctamente cuando los arrays se reordenan.
-## Model design best practices
+## Mejores prácticas de diseño de modelos
-Well-designed form models make forms easier to maintain and extend. Follow these patterns when designing your models.
+Los modelos de formularios bien diseñados hacen que los formularios sean más fáciles de mantener y extender. Sigue estos patrones al diseñar tus modelos.
-### Use specific types
+### Usa tipos específicos
-Always define interfaces or types for your models as shown in [Using TypeScript types](#using-typescript-types). Explicit types provide better IntelliSense, catch errors at compile time, and serve as documentation for what data the form contains.
+Siempre define interfaces o tipos para tus modelos como se muestra en [Usando tipos de TypeScript](#usando-tipos-de-typescript). Los tipos explícitos proporcionan mejor IntelliSense, capturan errores en tiempo de compilación y sirven como documentación de qué datos contiene el formulario.
-### Initialize all fields
+### Inicializa todos los campos
-Provide initial values for every field in your model:
+Proporciona valores iniciales para cada campo en tu modelo:
```ts
// Good: All fields initialized
@@ -451,11 +451,11 @@ const taskModel = signal({
})
```
-Missing initial values mean those fields won't exist in the field tree, making them inaccessible for form interactions.
+Faltar valores iniciales significa que esos campos no existirán en el field tree, haciéndolos inaccesibles para interacciones del formulario.
-### Keep models focused
+### Mantén los modelos enfocados
-Each model should represent a single form or a cohesive set of related data:
+Cada modelo debe representar un único formulario o un conjunto cohesivo de datos relacionados:
```ts
// Good: Focused on login
@@ -479,11 +479,11 @@ const appModel = signal({
})
```
-Separate models for different concerns makes forms easier to understand and reuse. Create multiple forms if you're managing distinct sets of data.
+Modelos separados para diferentes preocupaciones hace que los formularios sean más fáciles de entender y reutilizar. Crea múltiples formularios si estás gestionando conjuntos distintos de datos.
-### Consider validation requirements
+### Considera los requisitos de validación
-Design models with validation in mind. Group fields that validate together:
+Diseña modelos con la validación en mente. Agrupa campos que se validan juntos:
```ts
// Good: Password fields grouped for comparison
@@ -494,11 +494,11 @@ interface PasswordChangeData {
}
```
-This structure makes cross-field validation (like checking if `newPassword` matches `confirmPassword`) more natural.
+Esta estructura hace que la validación entre campos (como verificar si `newPassword` coincide con `confirmPassword`) sea más natural.
-### Plan for initial state
+### Planifica para el estado inicial
-Consider whether your form starts empty or pre-populated:
+Considera si tu formulario comienza vacío o pre-poblado:
```ts
// Form that starts empty (new user)
@@ -524,7 +524,7 @@ async loadExistingUser() {
}
```
-For forms that always start with existing data, you might wait to render the form until data loads in order to avoid a flash of empty fields.
+Para formularios que siempre comienzan con datos existentes, podrías esperar a renderizar el formulario hasta que los datos se carguen para evitar un destello de campos vacíos.
+
+
+CRITICAL: Signal Forms are [experimental](/reference/releases#experimental). The API may change in future releases. Avoid using experimental APIs in production applications without understanding the risks.
+
+Signal Forms is an experimental library that allows you to manage form state in Angular applications by building on the reactive foundation of signals. With automatic two-way binding, type-safe field access, and schema-based validation, Signal Forms help you create robust forms.
+
+TIP: For a quick introduction to Signal Forms, see the [Signal Forms essentials guide](essentials/signal-forms).
+
+## Why Signal Forms?
+
+Building forms in web applications involves managing several interconnected concerns: tracking field values, validating user input, handling error states, and keeping the UI synchronized with your data model. Managing these concerns separately creates boilerplate code and complexity.
+
+Signal Forms address these challenges by:
+
+- **Synchronizing state automatically** - Automatically syncs the form data model with bound form fields
+- **Providing type safety** - Supports fully type safe schemas & bindings between your UI controls and data model
+- **Centralizing validation logic** - Define all validation rules in one place using a validation schema
+
+Signal Forms work best in new applications built with signals. If you're working with an existing application that uses reactive forms, or if you need production stability guarantees, reactive forms remain a solid choice.
+
+
+
+
+## Prerequisites
+
+Signal Forms require:
+
+- Angular v21 or higher
+
+## Setup
+
+Signal Forms are already included in the `@angular/forms` package. Import the necessary functions and directives from `@angular/forms/signals`:
+
+```ts
+import { form, Field, required, email } from '@angular/forms/signals'
+```
+
+The `Field` directive must be imported into any component that binds form fields to HTML inputs:
+
+```ts
+@Component({
+ // ...
+ imports: [Field],
+})
+```
+
+
+
diff --git a/adev-es/src/content/guide/forms/signals/overview.md b/adev-es/src/content/guide/forms/signals/overview.md
index 73102a1..45a01f5 100644
--- a/adev-es/src/content/guide/forms/signals/overview.md
+++ b/adev-es/src/content/guide/forms/signals/overview.md
@@ -1,42 +1,42 @@
-
+
-CRITICAL: Signal Forms are [experimental](/reference/releases#experimental). The API may change in future releases. Avoid using experimental APIs in production applications without understanding the risks.
+IMPORTANTE: Signal Forms es [experimental](/reference/releases#experimental). La API puede cambiar en versiones futuras. Evita usar APIs experimentales en aplicaciones de producción sin comprender los riesgos.
-Signal Forms is an experimental library that allows you to manage form state in Angular applications by building on the reactive foundation of signals. With automatic two-way binding, type-safe field access, and schema-based validation, Signal Forms help you create robust forms.
+Signal Forms es una biblioteca experimental que te permite gestionar el estado de formularios en aplicaciones de Angular construyendo sobre la base reactiva de signals. Con enlace bidireccional automático, acceso a campos con seguridad de tipos y validación basada en esquemas, Signal Forms te ayudan a crear formularios robustos.
-TIP: For a quick introduction to Signal Forms, see the [Signal Forms essentials guide](essentials/signal-forms).
+CONSEJO: Para una introducción rápida a Signal Forms, consulta la [guía esencial de Signal Forms](essentials/signal-forms).
-## Why Signal Forms?
+## ¿Por qué Signal Forms?
-Building forms in web applications involves managing several interconnected concerns: tracking field values, validating user input, handling error states, and keeping the UI synchronized with your data model. Managing these concerns separately creates boilerplate code and complexity.
+Construir formularios en aplicaciones web implica gestionar varias preocupaciones interconectadas: rastrear valores de campos, validar la entrada del usuario, manejar estados de error y mantener la interfaz de usuario sincronizada con tu modelo de datos. Gestionar estas preocupaciones por separado crea código repetitivo y complejidad.
-Signal Forms address these challenges by:
+Signal Forms abordan estos desafíos mediante:
-- **Synchronizing state automatically** - Automatically syncs the form data model with bound form fields
-- **Providing type safety** - Supports fully type safe schemas & bindings between your UI controls and data model
-- **Centralizing validation logic** - Define all validation rules in one place using a validation schema
+- **Sincronización automática del estado** - Sincroniza automáticamente el modelo de datos del formulario con los campos del formulario vinculados
+- **Proporcionar seguridad de tipos** - Soporta esquemas y enlaces completamente seguros de tipos entre tus controles de interfaz de usuario y el modelo de datos
+- **Centralizar la lógica de validación** - Define todas las reglas de validación en un solo lugar usando un esquema de validación
-Signal Forms work best in new applications built with signals. If you're working with an existing application that uses reactive forms, or if you need production stability guarantees, reactive forms remain a solid choice.
+Signal Forms funcionan mejor en aplicaciones nuevas construidas con signals. Si estás trabajando con una aplicación existente que usa formularios reactivos, o si necesitas garantías de estabilidad en producción, los formularios reactivos siguen siendo una opción sólida.
-## Prerequisites
+## Requisitos previos
-Signal Forms require:
+Signal Forms requieren:
-- Angular v21 or higher
+- Angular v21 o superior
-## Setup
+## Configuración
-Signal Forms are already included in the `@angular/forms` package. Import the necessary functions and directives from `@angular/forms/signals`:
+Signal Forms ya están incluidos en el paquete `@angular/forms`. Importa las funciones y directivas necesarias desde `@angular/forms/signals`:
```ts
import { form, Field, required, email } from '@angular/forms/signals'
```
-The `Field` directive must be imported into any component that binds form fields to HTML inputs:
+La directiva `Field` debe importarse en cualquier componente que vincule campos de formulario a inputs HTML:
```ts
@Component({
diff --git a/adev-es/src/content/guide/forms/signals/validation.en.md b/adev-es/src/content/guide/forms/signals/validation.en.md
new file mode 100644
index 0000000..63ba327
--- /dev/null
+++ b/adev-es/src/content/guide/forms/signals/validation.en.md
@@ -0,0 +1,629 @@
+# Validation
+
+Forms need validation to ensure users provide correct, complete data before submission. Without validation, you would need to handle data quality issues on the server, provide poor user experience with unclear error messages, and manually check every constraint.
+
+Signal Forms provides a schema-based validation approach. Validation rules bind to fields using a schema function, run automatically when values change, and expose errors through field state signals. This enables reactive validation that updates as users interact with the form.
+
+
+
+
+
+
+
+## Validation basics
+
+Validation in Signal Forms is defined through a schema function passed as the second argument to `form()`.
+
+### The schema function
+
+The schema function receives a `SchemaPathTree` object that lets you define your validation rules:
+
+
+
+The schema function runs once during form initialization. Validation rules bind to fields using the schema path parameter (such as `schemaPath.email`, `schemaPath.password`), and validation runs automatically whenever field values change.
+
+NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+
+### How validation works
+
+Validation in Signal Forms follows this pattern:
+
+1. **Define validation rules in schema** - Bind validation rules to fields in the schema function
+2. **Automatic execution** - Validation rules run when field values change
+3. **Error propagation** - Validation errors are exposed through field state signals
+4. **Reactive updates** - UI automatically updates when validation state changes
+
+Validation runs on every value change for interactive fields. Hidden and disabled fields don't run validation - their validation rules are skipped until the field becomes interactive again.
+
+### Validation timing
+
+Validation rules execute in this order:
+
+1. **Synchronous validation** - All synchronous validation rules run when value changes
+2. **Asynchronous validation** - Asynchronous validation rules run only after all synchronous validation rules pass
+3. **Field state updates** - The `valid()`, `invalid()`, `errors()`, and `pending()` signals update
+
+Synchronous validation rules (like `required()`, `email()`) complete immediately. Asynchronous validation rules (like `validateHttp()`) may take time and set the `pending()` signal to `true` while executing.
+
+All validation rules run on every change - validation doesn't short-circuit after the first error. If a field has both `required()` and `email()` validation rules, both run, and both can produce errors simultaneously.
+
+## Built-in validation rules
+
+Signal Forms provides validation rules for common validation scenarios. All built-in validation rules accept an options object for custom error messages and conditional logic.
+
+### required()
+
+The `required()` validation rule ensures a field has a value:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, required } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-registration',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class RegistrationComponent {
+ registrationModel = signal({
+ username: '',
+ email: ''
+ })
+
+ registrationForm = form(this.registrationModel, (schemaPath) => {
+ required(schemaPath.username, { message: 'Username is required' })
+ required(schemaPath.email, { message: 'Email is required' })
+ })
+}
+```
+
+A field is considered "empty" when:
+
+| Condition | Example |
+| ------------------------ | ------- |
+| Value is `null` | `null`, |
+| Value is an empty string | `''` |
+| Value is an empty array | `[]` |
+
+For conditional requirements, use the `when` option:
+
+```ts
+registrationForm = form(this.registrationModel, (schemaPath) => {
+ required(schemaPath.promoCode, {
+ message: 'Promo code is required for discounts',
+ when: ({valueOf}) => valueOf(schemaPath.applyDiscount)
+ })
+})
+```
+
+The validation rule only runs when the `when` function returns `true`.
+
+### email()
+
+The `email()` validation rule checks for valid email format:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, email } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-contact',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class ContactComponent {
+ contactModel = signal({ email: '' })
+
+ contactForm = form(this.contactModel, (schemaPath) => {
+ email(schemaPath.email, { message: 'Please enter a valid email address' })
+ })
+}
+```
+
+The `email()` validation rule uses a standard email format regex. It accepts addresses like `user@example.com` but rejects malformed addresses like `user@` or `@example.com`.
+
+### min() and max()
+
+The `min()` and `max()` validation rules work with numeric values:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, min, max } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-age-form',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class AgeFormComponent {
+ ageModel = signal({
+ age: 0,
+ rating: 0
+ })
+
+ ageForm = form(this.ageModel, (schemaPath) => {
+ min(schemaPath.age, 18, { message: 'You must be at least 18 years old' })
+ max(schemaPath.age, 120, { message: 'Please enter a valid age' })
+
+ min(schemaPath.rating, 1, { message: 'Rating must be at least 1' })
+ max(schemaPath.rating, 5, { message: 'Rating cannot exceed 5' })
+ })
+}
+```
+
+You can use computed values for dynamic constraints:
+
+```ts
+ageForm = form(this.ageModel, (schemaPath) => {
+ min(schemaPath.participants, () => this.minimumRequired(), {
+ message: 'Not enough participants'
+ })
+})
+```
+
+### minLength() and maxLength()
+
+The `minLength()` and `maxLength()` validation rules work with strings and arrays:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, minLength, maxLength } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-password-form',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class PasswordFormComponent {
+ passwordModel = signal({
+ password: '',
+ bio: ''
+ })
+
+ passwordForm = form(this.passwordModel, (schemaPath) => {
+ minLength(schemaPath.password, 8, { message: 'Password must be at least 8 characters' })
+ maxLength(schemaPath.password, 100, { message: 'Password is too long' })
+
+ maxLength(schemaPath.bio, 500, { message: 'Bio cannot exceed 500 characters' })
+ })
+}
+```
+
+For strings, "length" means the number of characters. For arrays, "length" means the number of elements.
+
+### pattern()
+
+The `pattern()` validation rule validates against a regular expression:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, pattern } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-phone-form',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class PhoneFormComponent {
+ phoneModel = signal({
+ phone: '',
+ postalCode: ''
+ })
+
+ phoneForm = form(this.phoneModel, (schemaPath) => {
+ pattern(schemaPath.phone, /^\d{3}-\d{3}-\d{4}$/, {
+ message: 'Phone must be in format: 555-123-4567'
+ })
+
+ pattern(schemaPath.postalCode, /^\d{5}$/, {
+ message: 'Postal code must be 5 digits'
+ })
+ })
+}
+```
+
+Common patterns:
+
+| Pattern Type | Regular Expression | Example |
+| ---------------- | ----------------------- | ------------ |
+| Phone | `/^\d{3}-\d{3}-\d{4}$/` | 555-123-4567 |
+| Postal code (US) | `/^\d{5}$/` | 12345 |
+| Alphanumeric | `/^[a-zA-Z0-9]+$/` | abc123 |
+| URL-safe | `/^[a-zA-Z0-9_-]+$/` | my-url_123 |
+
+## Validation errors
+
+When validation rules fail, they produce error objects that describe what went wrong. Understanding error structure helps you provide clear feedback to users.
+
+
+
+### Error structure
+
+Each validation error object contains these properties:
+
+| Property | Description |
+| --------- | ------------------------------------------------------------------------ |
+| `kind` | The validation rule that failed (e.g., "required", "email", "minLength") |
+| `message` | Optional human-readable error message |
+
+Built-in validation rules automatically set the `kind` property. The `message` property is optional - you can provide custom messages through validation rule options.
+
+### Custom error messages
+
+All built-in validation rules accept a `message` option for custom error text:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, required, minLength } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-signup',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class SignupComponent {
+ signupModel = signal({
+ username: '',
+ password: ''
+ })
+
+ signupForm = form(this.signupModel, (schemaPath) => {
+ required(schemaPath.username, {
+ message: 'Please choose a username'
+ })
+
+ required(schemaPath.password, {
+ message: 'Password cannot be empty'
+ })
+ minLength(schemaPath.password, 12, {
+ message: 'Password must be at least 12 characters for security'
+ })
+ })
+}
+```
+
+Custom messages should be clear, specific, and tell users how to fix the problem. Instead of "Invalid input", use "Password must be at least 12 characters for security".
+
+### Multiple errors per field
+
+When a field has multiple validation rules, each validation rule runs independently and can produce an error:
+
+```ts
+signupForm = form(this.signupModel, (schemaPath) => {
+ required(schemaPath.email, { message: 'Email is required' })
+ email(schemaPath.email, { message: 'Enter a valid email address' })
+ minLength(schemaPath.email, 5, { message: 'Email is too short' })
+})
+```
+
+If the email field is empty, only the `required()` error appears. If the user types "a@b", both `email()` and `minLength()` errors appear. All validation rules run - validation doesn't stop after the first failure.
+
+TIP: Use the `touched() && invalid()` pattern in your templates to prevent errors from appearing before users have interacted with a field. For comprehensive guidance on displaying validation errors, see the [Field State Management guide](guide/forms/signals/field-state-management#conditional-error-display).
+
+## Custom validation rules
+
+While built-in validation rules handle common cases, you'll often need custom validation logic for business rules, complex formats, or domain-specific constraints.
+
+### Using validate()
+
+The `validate()` function creates custom validation rules. It receives a validator function that accesses the field context and returns:
+
+| Return Value | Meaning |
+| --------------------- | ---------------- |
+| Error object | Value is invalid |
+| `null` or `undefined` | Value is valid |
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, validate } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-url-form',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class UrlFormComponent {
+ urlModel = signal({ website: '' })
+
+ urlForm = form(this.urlModel, (schemaPath) => {
+ validate(schemaPath.website, ({value}) => {
+ if (!value().startsWith('https://')) {
+ return {
+ kind: 'https',
+ message: 'URL must start with https://'
+ }
+ }
+
+ return null
+ })
+ })
+}
+```
+
+The validator function receives a `FieldContext` object with:
+
+| Property | Type | Description |
+| --------------- | ---------- | ------------------------------------------- |
+| `value` | Signal | Signal containing the current field value |
+| `state` | FieldState | The field state reference |
+| `field` | FieldTree | The field tree reference |
+| `valueOf()` | Method | Get the value of another field by path |
+| `stateOf()` | Method | Get the state of another field by path |
+| `fieldTreeOf()` | Method | Get the field tree of another field by path |
+| `pathKeys` | Signal | Path keys from root to current field |
+
+NOTE: Child fields also have a `key` signal, and array item fields have both `key` and `index` signals.
+
+Return an error object with `kind` and `message` when validation fails. Return `null` or `undefined` when validation passes.
+
+### Reusable validation rules
+
+Create reusable validation rule functions by wrapping `validate()`:
+
+```ts
+function url(field: any, options?: { message?: string }) {
+ validate(field, ({value}) => {
+ try {
+ new URL(value())
+ return null
+ } catch {
+ return {
+ kind: 'url',
+ message: options?.message || 'Enter a valid URL'
+ }
+ }
+ })
+}
+
+function phoneNumber(field: any, options?: { message?: string }) {
+ validate(field, ({value}) => {
+ const phoneRegex = /^\d{3}-\d{3}-\d{4}$/
+
+ if (!phoneRegex.test(value())) {
+ return {
+ kind: 'phoneNumber',
+ message: options?.message || 'Phone must be in format: 555-123-4567'
+ }
+ }
+
+ return null
+ })
+}
+```
+
+You can use custom validation rules just like built-in validation rules:
+
+```ts
+urlForm = form(this.urlModel, (schemaPath) => {
+ url(schemaPath.website, { message: 'Please enter a valid website URL' })
+ phoneNumber(schemaPath.phone)
+})
+```
+
+## Cross-field validation
+
+Cross-field validation compares or relates multiple field values.
+
+A common scenario for cross-field validation is password confirmation:
+
+```angular-ts
+import { Component, signal } from '@angular/core'
+import { form, Field, required, minLength, validate } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-password-change',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class PasswordChangeComponent {
+ passwordModel = signal({
+ password: '',
+ confirmPassword: ''
+ })
+
+ passwordForm = form(this.passwordModel, (schemaPath) => {
+ required(schemaPath.password, { message: 'Password is required' })
+ minLength(schemaPath.password, 8, { message: 'Password must be at least 8 characters' })
+
+ required(schemaPath.confirmPassword, { message: 'Please confirm your password' })
+
+ validate(schemaPath.confirmPassword, ({value, valueOf}) => {
+ const confirmPassword = value()
+ const password = valueOf(schemaPath.password)
+
+ if (confirmPassword !== password) {
+ return {
+ kind: 'passwordMismatch',
+ message: 'Passwords do not match'
+ }
+ }
+
+ return null
+ })
+ })
+}
+```
+
+The confirmation validation rule accesses the password field value using `valueOf(schemaPath.password)` and compares it to the confirmation value. This validation rule runs reactively - if either password changes, validation reruns automatically.
+
+## Async validation
+
+Async validation handles validation that requires external data sources, like checking username availability on a server or validating against an API.
+
+### Using validateHttp()
+
+The `validateHttp()` function performs HTTP-based validation:
+
+```angular-ts
+import { Component, signal, inject } from '@angular/core'
+import { HttpClient } from '@angular/common/http'
+import { form, Field, required, validateHttp } from '@angular/forms/signals'
+
+@Component({
+ selector: 'app-username-form',
+ imports: [Field],
+ template: `
+
+ `
+})
+export class UsernameFormComponent {
+ http = inject(HttpClient)
+
+ usernameModel = signal({ username: '' })
+
+ usernameForm = form(this.usernameModel, (schemaPath) => {
+ required(schemaPath.username, { message: 'Username is required' })
+
+ validateHttp(schemaPath.username, {
+ request: ({value}) => `/api/check-username?username=${value()}`,
+ onSuccess: (response: any) => {
+ if (response.taken) {
+ return {
+ kind: 'usernameTaken',
+ message: 'Username is already taken'
+ }
+ }
+ return null
+ },
+ onError: (error) => ({
+ kind: 'networkError',
+ message: 'Could not verify username availability'
+ })
+ })
+ })
+}
+```
+
+The `validateHttp()` validation rule:
+
+1. Calls the URL or request returned by the `request` function
+2. Maps the successful response to a validation error or `null` using `onSuccess`
+3. Handles request failures (network errors, HTTP errors) using `onError`
+4. Sets `pending()` to `true` while the request is in progress
+5. Only runs after all synchronous validation rules pass
+
+### Pending state
+
+While async validation runs, the field's `pending()` signal returns `true`. Use this to show loading indicators:
+
+```ts
+@if (form.username().pending()) {
+ Checking...
+}
+```
+
+The `valid()` signal returns `false` while validation is pending, even if there are no errors yet. The `invalid()` signal only returns `true` if errors exist.
+
+## Next steps
+
+This guide covered creating and applying validation rules. Related guides explore other aspects of Signal Forms:
+
+- [Form Models guide](guide/forms/signals/models) - Creating and updating form models
+
+
diff --git a/adev-es/src/content/guide/forms/signals/validation.md b/adev-es/src/content/guide/forms/signals/validation.md
index 63ba327..3653bc3 100644
--- a/adev-es/src/content/guide/forms/signals/validation.md
+++ b/adev-es/src/content/guide/forms/signals/validation.md
@@ -1,8 +1,8 @@
-# Validation
+# Validación
-Forms need validation to ensure users provide correct, complete data before submission. Without validation, you would need to handle data quality issues on the server, provide poor user experience with unclear error messages, and manually check every constraint.
+Los formularios necesitan validación para asegurar que los usuarios proporcionen datos correctos y completos antes del envío. Sin validación, tendrías que manejar problemas de calidad de datos en el servidor, proporcionar una mala experiencia de usuario con mensajes de error poco claros, y verificar manualmente cada restricción.
-Signal Forms provides a schema-based validation approach. Validation rules bind to fields using a schema function, run automatically when values change, and expose errors through field state signals. This enables reactive validation that updates as users interact with the form.
+Signal Forms proporciona un enfoque de validación basado en esquemas. Las reglas de validación se vinculan a los campos usando una función de esquema, se ejecutan automáticamente cuando los valores cambian, y exponen errores a través de signals de estado de campo. Esto permite una validación reactiva que se actualiza a medida que los usuarios interactúan con el formulario.
@@ -10,13 +10,13 @@ Signal Forms provides a schema-based validation approach. Validation rules bind
-## Validation basics
+## Fundamentos de validación
-Validation in Signal Forms is defined through a schema function passed as the second argument to `form()`.
+La validación en Signal Forms se define a través de una función de esquema pasada como segundo argumento a `form()`.
-### The schema function
+### La función de esquema
-The schema function receives a `SchemaPathTree` object that lets you define your validation rules:
+La función de esquema recibe un objeto `SchemaPathTree` que te permite definir tus reglas de validación:
-The schema function runs once during form initialization. Validation rules bind to fields using the schema path parameter (such as `schemaPath.email`, `schemaPath.password`), and validation runs automatically whenever field values change.
+La función de esquema se ejecuta una vez durante la inicialización del formulario. Las reglas de validación se vinculan a los campos usando el parámetro de ruta de esquema (como `schemaPath.email`, `schemaPath.password`), y la validación se ejecuta automáticamente cuando los valores de los campos cambian.
-NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like.
+NOTA: El parámetro callback del esquema (`schemaPath` en estos ejemplos) es un objeto `SchemaPathTree` que proporciona rutas a todos los campos en tu formulario. Puedes nombrar este parámetro como desees.
-### How validation works
+### Cómo funciona la validación
-Validation in Signal Forms follows this pattern:
+La validación en Signal Forms sigue este patrón:
-1. **Define validation rules in schema** - Bind validation rules to fields in the schema function
-2. **Automatic execution** - Validation rules run when field values change
-3. **Error propagation** - Validation errors are exposed through field state signals
-4. **Reactive updates** - UI automatically updates when validation state changes
+1. **Definir reglas de validación en el esquema** - Vincular reglas de validación a los campos en la función de esquema
+2. **Ejecución automática** - Las reglas de validación se ejecutan cuando los valores de los campos cambian
+3. **Propagación de errores** - Los errores de validación se exponen a través de signals de estado de campo
+4. **Actualizaciones reactivas** - La UI se actualiza automáticamente cuando el estado de validación cambia
-Validation runs on every value change for interactive fields. Hidden and disabled fields don't run validation - their validation rules are skipped until the field becomes interactive again.
+La validación se ejecuta en cada cambio de valor para campos interactivos. Los campos ocultos y deshabilitados no ejecutan validación - sus reglas de validación se omiten hasta que el campo se vuelve interactivo nuevamente.
-### Validation timing
+### Momento de la validación
-Validation rules execute in this order:
+Las reglas de validación se ejecutan en este orden:
-1. **Synchronous validation** - All synchronous validation rules run when value changes
-2. **Asynchronous validation** - Asynchronous validation rules run only after all synchronous validation rules pass
-3. **Field state updates** - The `valid()`, `invalid()`, `errors()`, and `pending()` signals update
+1. **Validación síncrona** - Todas las reglas de validación síncronas se ejecutan cuando el valor cambia
+2. **Validación asíncrona** - Las reglas de validación asíncronas se ejecutan solo después de que todas las reglas de validación síncronas pasen
+3. **Actualizaciones de estado de campo** - Los signals `valid()`, `invalid()`, `errors()`, y `pending()` se actualizan
-Synchronous validation rules (like `required()`, `email()`) complete immediately. Asynchronous validation rules (like `validateHttp()`) may take time and set the `pending()` signal to `true` while executing.
+Las reglas de validación síncronas (como `required()`, `email()`) se completan inmediatamente. Las reglas de validación asíncronas (como `validateHttp()`) pueden tomar tiempo y establecen el signal `pending()` en `true` mientras se ejecutan.
-All validation rules run on every change - validation doesn't short-circuit after the first error. If a field has both `required()` and `email()` validation rules, both run, and both can produce errors simultaneously.
+Todas las reglas de validación se ejecutan en cada cambio - la validación no se detiene después del primer error. Si un campo tiene reglas de validación `required()` y `email()`, ambas se ejecutan, y ambas pueden producir errores simultáneamente.
-## Built-in validation rules
+## Reglas de validación integradas
-Signal Forms provides validation rules for common validation scenarios. All built-in validation rules accept an options object for custom error messages and conditional logic.
+Signal Forms proporciona reglas de validación para escenarios de validación comunes. Todas las reglas de validación integradas aceptan un objeto de opciones para mensajes de error personalizados y lógica condicional.
### required()
-The `required()` validation rule ensures a field has a value:
+La regla de validación `required()` asegura que un campo tenga un valor:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -96,15 +96,15 @@ export class RegistrationComponent {
}
```
-A field is considered "empty" when:
+Un campo se considera "vacío" cuando:
-| Condition | Example |
-| ------------------------ | ------- |
-| Value is `null` | `null`, |
-| Value is an empty string | `''` |
-| Value is an empty array | `[]` |
+| Condición | Ejemplo |
+| --------------------------- | ------- |
+| El valor es `null` | `null`, |
+| El valor es una cadena vacía| `''` |
+| El valor es un array vacío | `[]` |
-For conditional requirements, use the `when` option:
+Para requisitos condicionales, usa la opción `when`:
```ts
registrationForm = form(this.registrationModel, (schemaPath) => {
@@ -115,11 +115,11 @@ registrationForm = form(this.registrationModel, (schemaPath) => {
})
```
-The validation rule only runs when the `when` function returns `true`.
+La regla de validación solo se ejecuta cuando la función `when` devuelve `true`.
### email()
-The `email()` validation rule checks for valid email format:
+La regla de validación `email()` verifica un formato de email válido:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -146,11 +146,11 @@ export class ContactComponent {
}
```
-The `email()` validation rule uses a standard email format regex. It accepts addresses like `user@example.com` but rejects malformed addresses like `user@` or `@example.com`.
+La regla de validación `email()` usa una expresión regular de formato de email estándar. Acepta direcciones como `user@example.com` pero rechaza direcciones malformadas como `user@` o `@example.com`.
-### min() and max()
+### min() y max()
-The `min()` and `max()` validation rules work with numeric values:
+Las reglas de validación `min()` y `max()` funcionan con valores numéricos:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -189,7 +189,7 @@ export class AgeFormComponent {
}
```
-You can use computed values for dynamic constraints:
+Puedes usar valores computed para restricciones dinámicas:
```ts
ageForm = form(this.ageModel, (schemaPath) => {
@@ -199,9 +199,9 @@ ageForm = form(this.ageModel, (schemaPath) => {
})
```
-### minLength() and maxLength()
+### minLength() y maxLength()
-The `minLength()` and `maxLength()` validation rules work with strings and arrays:
+Las reglas de validación `minLength()` y `maxLength()` funcionan con cadenas y arrays:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -239,11 +239,11 @@ export class PasswordFormComponent {
}
```
-For strings, "length" means the number of characters. For arrays, "length" means the number of elements.
+Para cadenas, "longitud" significa el número de caracteres. Para arrays, "longitud" significa el número de elementos.
### pattern()
-The `pattern()` validation rule validates against a regular expression:
+La regla de validación `pattern()` valida contra una expresión regular:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -284,37 +284,37 @@ export class PhoneFormComponent {
}
```
-Common patterns:
+Patrones comunes:
-| Pattern Type | Regular Expression | Example |
-| ---------------- | ----------------------- | ------------ |
-| Phone | `/^\d{3}-\d{3}-\d{4}$/` | 555-123-4567 |
-| Postal code (US) | `/^\d{5}$/` | 12345 |
-| Alphanumeric | `/^[a-zA-Z0-9]+$/` | abc123 |
-| URL-safe | `/^[a-zA-Z0-9_-]+$/` | my-url_123 |
+| Tipo de Patrón | Expresión Regular | Ejemplo |
+| -------------------- | ----------------------- | ------------ |
+| Teléfono | `/^\d{3}-\d{3}-\d{4}$/` | 555-123-4567 |
+| Código postal (US) | `/^\d{5}$/` | 12345 |
+| Alfanumérico | `/^[a-zA-Z0-9]+$/` | abc123 |
+| Seguro para URL | `/^[a-zA-Z0-9_-]+$/` | my-url_123 |
-## Validation errors
+## Errores de validación
-When validation rules fail, they produce error objects that describe what went wrong. Understanding error structure helps you provide clear feedback to users.
+Cuando las reglas de validación fallan, producen objetos de error que describen qué salió mal. Entender la estructura de errores te ayuda a proporcionar comentarios claros a los usuarios.
+NOTA: Esta sección cubre los errores que producen las reglas de validación. Para mostrar y usar errores de validación en tu UI, consulta la [guía de Gestión de Estado de Campo](guide/forms/signals/field-state-management). -->
-### Error structure
+### Estructura del error
-Each validation error object contains these properties:
+Cada objeto de error de validación contiene estas propiedades:
-| Property | Description |
-| --------- | ------------------------------------------------------------------------ |
-| `kind` | The validation rule that failed (e.g., "required", "email", "minLength") |
-| `message` | Optional human-readable error message |
+| Propiedad | Descripción |
+| --------- | --------------------------------------------------------------------------- |
+| `kind` | La regla de validación que falló (ej., "required", "email", "minLength") |
+| `message` | Mensaje de error legible opcional |
-Built-in validation rules automatically set the `kind` property. The `message` property is optional - you can provide custom messages through validation rule options.
+Las reglas de validación integradas establecen automáticamente la propiedad `kind`. La propiedad `message` es opcional - puedes proporcionar mensajes personalizados a través de las opciones de regla de validación.
-### Custom error messages
+### Mensajes de error personalizados
-All built-in validation rules accept a `message` option for custom error text:
+Todas las reglas de validación integradas aceptan una opción `message` para texto de error personalizado:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -358,11 +358,11 @@ export class SignupComponent {
}
```
-Custom messages should be clear, specific, and tell users how to fix the problem. Instead of "Invalid input", use "Password must be at least 12 characters for security".
+Los mensajes personalizados deben ser claros, específicos, y decirle a los usuarios cómo solucionar el problema. En lugar de "Invalid input", usa "Password must be at least 12 characters for security".
-### Multiple errors per field
+### Múltiples errores por campo
-When a field has multiple validation rules, each validation rule runs independently and can produce an error:
+Cuando un campo tiene múltiples reglas de validación, cada regla de validación se ejecuta independientemente y puede producir un error:
```ts
signupForm = form(this.signupModel, (schemaPath) => {
@@ -372,22 +372,22 @@ signupForm = form(this.signupModel, (schemaPath) => {
})
```
-If the email field is empty, only the `required()` error appears. If the user types "a@b", both `email()` and `minLength()` errors appear. All validation rules run - validation doesn't stop after the first failure.
+Si el campo de email está vacío, solo aparece el error de `required()`. Si el usuario escribe "a@b", aparecen ambos errores de `email()` y `minLength()`. Todas las reglas de validación se ejecutan - la validación no se detiene después del primer fallo.
-TIP: Use the `touched() && invalid()` pattern in your templates to prevent errors from appearing before users have interacted with a field. For comprehensive guidance on displaying validation errors, see the [Field State Management guide](guide/forms/signals/field-state-management#conditional-error-display).
+CONSEJO: Usa el patrón `touched() && invalid()` en tus plantillas para evitar que los errores aparezcan antes de que los usuarios hayan interactuado con un campo. Para una guía completa sobre cómo mostrar errores de validación, consulta la [guía de Gestión de Estado de Campo](guide/forms/signals/field-state-management#conditional-error-display).
-## Custom validation rules
+## Reglas de validación personalizadas
-While built-in validation rules handle common cases, you'll often need custom validation logic for business rules, complex formats, or domain-specific constraints.
+Aunque las reglas de validación integradas manejan casos comunes, a menudo necesitarás lógica de validación personalizada para reglas de negocio, formatos complejos, o restricciones específicas del dominio.
-### Using validate()
+### Usando validate()
-The `validate()` function creates custom validation rules. It receives a validator function that accesses the field context and returns:
+La función `validate()` crea reglas de validación personalizadas. Recibe una función validadora que accede al contexto del campo y devuelve:
-| Return Value | Meaning |
-| --------------------- | ---------------- |
-| Error object | Value is invalid |
-| `null` or `undefined` | Value is valid |
+| Valor de Retorno | Significado |
+| --------------------- | ----------------- |
+| Objeto de error | El valor es inválido |
+| `null` o `undefined` | El valor es válido |
```angular-ts
import { Component, signal } from '@angular/core'
@@ -423,25 +423,25 @@ export class UrlFormComponent {
}
```
-The validator function receives a `FieldContext` object with:
+La función validadora recibe un objeto `FieldContext` con:
-| Property | Type | Description |
-| --------------- | ---------- | ------------------------------------------- |
-| `value` | Signal | Signal containing the current field value |
-| `state` | FieldState | The field state reference |
-| `field` | FieldTree | The field tree reference |
-| `valueOf()` | Method | Get the value of another field by path |
-| `stateOf()` | Method | Get the state of another field by path |
-| `fieldTreeOf()` | Method | Get the field tree of another field by path |
-| `pathKeys` | Signal | Path keys from root to current field |
+| Propiedad | Tipo | Descripción |
+| --------------- | ---------- | -------------------------------------------------- |
+| `value` | Signal | Signal que contiene el valor actual del campo |
+| `state` | FieldState | La referencia al estado del campo |
+| `field` | FieldTree | La referencia al árbol de campo |
+| `valueOf()` | Método | Obtener el valor de otro campo por ruta |
+| `stateOf()` | Método | Obtener el estado de otro campo por ruta |
+| `fieldTreeOf()` | Método | Obtener el árbol de campo de otro campo por ruta |
+| `pathKeys` | Signal | Claves de ruta desde la raíz hasta el campo actual |
-NOTE: Child fields also have a `key` signal, and array item fields have both `key` and `index` signals.
+NOTA: Los campos hijos también tienen un signal `key`, y los campos de elementos de array tienen tanto `key` como `index` signals.
-Return an error object with `kind` and `message` when validation fails. Return `null` or `undefined` when validation passes.
+Devuelve un objeto de error con `kind` y `message` cuando la validación falla. Devuelve `null` o `undefined` cuando la validación pasa.
-### Reusable validation rules
+### Reglas de validación reutilizables
-Create reusable validation rule functions by wrapping `validate()`:
+Crea funciones de reglas de validación reutilizables envolviendo `validate()`:
```ts
function url(field: any, options?: { message?: string }) {
@@ -474,7 +474,7 @@ function phoneNumber(field: any, options?: { message?: string }) {
}
```
-You can use custom validation rules just like built-in validation rules:
+Puedes usar reglas de validación personalizadas igual que las reglas de validación integradas:
```ts
urlForm = form(this.urlModel, (schemaPath) => {
@@ -483,11 +483,11 @@ urlForm = form(this.urlModel, (schemaPath) => {
})
```
-## Cross-field validation
+## Validación entre campos
-Cross-field validation compares or relates multiple field values.
+La validación entre campos compara o relaciona valores de múltiples campos.
-A common scenario for cross-field validation is password confirmation:
+Un escenario común para validación entre campos es la confirmación de contraseña:
```angular-ts
import { Component, signal } from '@angular/core'
@@ -541,15 +541,15 @@ export class PasswordChangeComponent {
}
```
-The confirmation validation rule accesses the password field value using `valueOf(schemaPath.password)` and compares it to the confirmation value. This validation rule runs reactively - if either password changes, validation reruns automatically.
+La regla de validación de confirmación accede al valor del campo de contraseña usando `valueOf(schemaPath.password)` y lo compara con el valor de confirmación. Esta regla de validación se ejecuta de forma reactiva - si cualquiera de las contraseñas cambia, la validación se vuelve a ejecutar automáticamente.
-## Async validation
+## Validación asíncrona
-Async validation handles validation that requires external data sources, like checking username availability on a server or validating against an API.
+La validación asíncrona maneja validación que requiere fuentes de datos externas, como verificar la disponibilidad de nombre de usuario en un servidor o validar contra una API.
-### Using validateHttp()
+### Usando validateHttp()
-The `validateHttp()` function performs HTTP-based validation:
+La función `validateHttp()` realiza validación basada en HTTP:
```angular-ts
import { Component, signal, inject } from '@angular/core'
@@ -600,17 +600,17 @@ export class UsernameFormComponent {
}
```
-The `validateHttp()` validation rule:
+La regla de validación `validateHttp()`:
-1. Calls the URL or request returned by the `request` function
-2. Maps the successful response to a validation error or `null` using `onSuccess`
-3. Handles request failures (network errors, HTTP errors) using `onError`
-4. Sets `pending()` to `true` while the request is in progress
-5. Only runs after all synchronous validation rules pass
+1. Llama a la URL o petición devuelta por la función `request`
+2. Mapea la respuesta exitosa a un error de validación o `null` usando `onSuccess`
+3. Maneja fallos de petición (errores de red, errores HTTP) usando `onError`
+4. Establece `pending()` en `true` mientras la petición está en progreso
+5. Solo se ejecuta después de que todas las reglas de validación síncronas pasen
-### Pending state
+### Estado pendiente
-While async validation runs, the field's `pending()` signal returns `true`. Use this to show loading indicators:
+Mientras se ejecuta la validación asíncrona, el signal `pending()` del campo devuelve `true`. Usa esto para mostrar indicadores de carga:
```ts
@if (form.username().pending()) {
@@ -618,12 +618,12 @@ While async validation runs, the field's `pending()` signal returns `true`. Use
}
```
-The `valid()` signal returns `false` while validation is pending, even if there are no errors yet. The `invalid()` signal only returns `true` if errors exist.
+El signal `valid()` devuelve `false` mientras la validación está pendiente, incluso si aún no hay errores. El signal `invalid()` solo devuelve `true` si existen errores.
-## Next steps
+## Próximos pasos
-This guide covered creating and applying validation rules. Related guides explore other aspects of Signal Forms:
+Esta guía cubrió la creación y aplicación de reglas de validación. Las guías relacionadas exploran otros aspectos de Signal Forms:
-- [Form Models guide](guide/forms/signals/models) - Creating and updating form models
+- [Guía de Modelos de Formulario](guide/forms/signals/models) - Creando y actualizando modelos de formulario
-
+