From ca1cb22325593c5c97a3195a70a60cb25068fdfe Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Fri, 21 Nov 2025 10:27:56 +0900 Subject: [PATCH] feat(docs): translate signal forms guides to Japanese Translate the following signal forms documentation files: - models.md - field-state-management.md - validation.md - custom-controls.md - comparison.md --- .../guide/forms/signals/comparison.en.md | 163 ++++ .../content/guide/forms/signals/comparison.md | 136 ++-- .../guide/forms/signals/custom-controls.en.md | 428 +++++++++++ .../guide/forms/signals/custom-controls.md | 202 ++--- .../signals/field-state-management.en.md | 695 ++++++++++++++++++ .../forms/signals/field-state-management.md | 300 ++++---- .../content/guide/forms/signals/models.en.md | 536 ++++++++++++++ .../src/content/guide/forms/signals/models.md | 200 ++--- .../guide/forms/signals/validation.en.md | 629 ++++++++++++++++ .../content/guide/forms/signals/validation.md | 218 +++--- 10 files changed, 2979 insertions(+), 528 deletions(-) create mode 100644 adev-ja/src/content/guide/forms/signals/comparison.en.md create mode 100644 adev-ja/src/content/guide/forms/signals/custom-controls.en.md create mode 100644 adev-ja/src/content/guide/forms/signals/field-state-management.en.md create mode 100644 adev-ja/src/content/guide/forms/signals/models.en.md create mode 100644 adev-ja/src/content/guide/forms/signals/validation.en.md diff --git a/adev-ja/src/content/guide/forms/signals/comparison.en.md b/adev-ja/src/content/guide/forms/signals/comparison.en.md new file mode 100644 index 0000000000..209e70e06e --- /dev/null +++ b/adev-ja/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/signal-forms/overview) to get started, or dive into [Form Models](guide/forms/signal-forms/models), [Validation](guide/forms/signal-forms/validation), and [Field State Management](guide/forms/signal-forms/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-ja/src/content/guide/forms/signals/comparison.md b/adev-ja/src/content/guide/forms/signals/comparison.md index 209e70e06e..a5d011f4a0 100644 --- a/adev-ja/src/content/guide/forms/signals/comparison.md +++ b/adev-ja/src/content/guide/forms/signals/comparison.md @@ -1,69 +1,69 @@ -# 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. +Angularは、シグナルフォーム、リアクティブフォーム、テンプレート駆動フォームという3つのフォーム構築アプローチを提供します。それぞれ、状態管理、バリデーション、データフローのための異なるパターンを持っています。このガイドは、その違いを理解し、あなたのプロジェクトに適したアプローチを選択するのに役立ちます。 -NOTE: Signal Forms are [experimental](reference/releases#experimental) as of Angular v21. The API may change before stabilizing. +NOTE: シグナルフォームはAngular v21の時点では[experimental](reference/releases#experimental)です。APIは安定化する前に変更される可能性があります。 -## Quick comparison +## クイック比較 {#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 | +| 信頼できる情報源 | ユーザー定義の書き込み可能なシグナルモデル | `FormControl`/`FormGroup` | コンポーネント内のユーザーモデル | +| 型安全性 | モデルから推論 | 型付きフォームで明示的に指定 | 最小限 | +| バリデーション | パスベースのバリデーターを持つスキーマ | コントロールに渡されるバリデーターのリスト | ディレクティブベース | +| 状態管理 | シグナルベース | Observableベース | Angularによる管理 | +| セットアップ | シグナル + スキーマ関数 | FormControlツリー | テンプレート内のNgModel | +| 最適な用途 | シグナルベースのアプリケーション | 複雑なフォーム | シンプルなフォーム | +| 学習曲線 | 中 | 中〜高 | 低 | +| ステータス | 実験的 (v21+) | 安定 | 安定 | -## By example: Login form +## 例: ログインフォーム {#by-example-login-form} -The best way to understand the differences is to see the same form implemented in all three approaches. +違いを理解する最善の方法は、3つのアプローチすべてで実装された同じフォームを見ることです。 - - - + + + -## 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. +3つのアプローチは、フォームの作成と保守の方法に影響を与える異なる設計上の選択をしています。これらの違いは、各アプローチがフォームの状態をどこに保存し、バリデーションをどのように管理するかに起因します。 -### Where your form data lives +### フォームデータがどこに存在するか {#where-your-form-data-lives} -The most fundamental difference is where each approach considers the "source of truth" for form values. +最も根本的な違いは、各アプローチがフォームの値の「信頼できる情報源 (source of truth)」をどこに置くかです。 -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. +これにより、フォームデータは単一のリアクティブなコンテナに保持され、値が変更されると自動的にAngularに通知されます。フォームの構造は、データモデルを正確に反映します。 -Reactive Forms stores data inside FormControl and FormGroup instances. You access values through the form hierarchy: +Reactive Formsは、`FormControl`と`FormGroup`のインスタンス内にデータを保存します。フォームの階層を通じて値にアクセスします。 ```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. +これは最も直接的なアプローチですが、値が必要なときに手動で組み立てる必要があります。Angularは、テンプレート内のディレクティブを通じてフォームの状態を管理します。 -### How validation works +### バリデーションの仕組み {#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) => { @@ -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. +すべてのバリデーションルールが1か所にまとめられます。スキーマ関数はフォーム作成時に一度だけ実行され、フィールドの値が変更されるとバリデーターが自動的に実行されます。エラーメッセージはバリデーション定義の一部です。 -Reactive Forms attaches validators when creating controls: +Reactive Formsは、コントロールを作成するときにバリデーターをアタッチします。 ```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. +バリデーターは、フォーム構造内の個々のコントロールに結び付けられます。これにより、バリデーションがフォーム定義全体に分散されます。エラーメッセージは通常、テンプレート内に記述します。 -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. +バリデーションルールは、HTMLとともにテンプレート内に記述されます。これにより、バリデーションはUIの近くに保たれますが、ロジックはテンプレートとコンポーネントに分散します。 -### Type safety and autocomplete +### 型安全性とオートコンプリート {#type-safety-and-autocomplete} -TypeScript integration differs significantly between approaches, affecting how much the compiler helps you avoid errors. +TypeScriptの統合はアプローチによって大きく異なり、コンパイラがエラーを回避するのにどれだけ役立つかに影響します。 -Signal Forms infers types from your model structure: +シグナルフォームは、モデル構造から型を推論します。 ```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. +シグナルでデータ構造を一度定義すると、TypeScriptはどのフィールドが存在し、その型が何かを自動的に認識します。`loginForm.username`(存在しない)にアクセスすると、型エラーが発生します。 -Reactive Forms requires explicit type annotations with typed forms: +Reactive Formsは、型付きフォームで明示的な型アノテーションを必要とします。 ```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. +各コントロールの型を個別に指定します。TypeScriptはフォーム構造を検証しますが、型情報はデータモデルとは別に保守します。 -Template-driven Forms offers minimal type safety: +テンプレート駆動フォームは、最小限の型安全性しか提供しません。 ```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はコンポーネントのプロパティを理解しますが、フォームの構造やバリデーションについては何も知りません。フォーム操作に対するコンパイル時チェックが失われます。 -## Choose your approach +## アプローチを選択する -### Use Signal Forms if: +### 次の場合はシグナルフォームを使用します: {#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 +- 新しいシグナルベースのアプリケーションを構築している (Angular v21+) +- モデル構造から推論される型安全性が欲しい +- 実験的な機能の利用に抵抗がない +- スキーマベースのバリデーションに魅力を感じる +- チームがシグナルに精通している -### Use Reactive Forms if: +### 次の場合はReactive Formsを使用します: {#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 +- 本番環境で利用できる安定性が必要である +- 複雑で動的なフォームを構築している +- Observableベースのパターンを好む +- フォームの状態をきめ細かく制御する必要がある +- 既存のリアクティブフォームのコードベースで作業している -### Use Template-driven Forms if: +### 次の場合はTemplate-driven Formsを使用します: {#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 +## 次のステップ {#next-steps} -To learn more about each approach: +各アプローチについてさらに学ぶには: -- **Signal Forms**: See the [Overview guide](guide/forms/signal-forms/overview) to get started, or dive into [Form Models](guide/forms/signal-forms/models), [Validation](guide/forms/signal-forms/validation), and [Field State Management](guide/forms/signal-forms/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 +- **シグナルフォーム**: [概要ガイド](guide/forms/signal-forms/overview)から始めるか、[フォームモデル](guide/forms/signal-forms/models)、[バリデーション](guide/forms/signal-forms/validation)、[フィールド状態管理](guide/forms/signal-forms/field-state-management)を詳しく見てください +- **リアクティブフォーム**: Angularドキュメントの[リアクティブフォームガイド](guide/forms/reactive-forms)を参照してください +- **テンプレート駆動フォーム**: Angularドキュメントの[テンプレート駆動フォームガイド](guide/forms/template-driven-forms)を参照してください diff --git a/adev-ja/src/content/guide/forms/signals/custom-controls.en.md b/adev-ja/src/content/guide/forms/signals/custom-controls.en.md new file mode 100644 index 0000000000..ba33ed4f54 --- /dev/null +++ b/adev-ja/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/signal-forms/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()) { +
+ + + @if (invalid()) { + + } + + @if (disabled() && disabledReasons().length > 0) { +
+ @for (reason of disabledReasons(); track reason) { + {{ reason.message }} + } +
+ } +
+ } + `, + 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/signal-forms/models) - Creating and updating form models + + + diff --git a/adev-ja/src/content/guide/forms/signals/custom-controls.md b/adev-ja/src/content/guide/forms/signals/custom-controls.md index ba33ed4f54..00b90fbe76 100644 --- a/adev-ja/src/content/guide/forms/signals/custom-controls.md +++ b/adev-ja/src/content/guide/forms/signals/custom-controls.md @@ -1,18 +1,18 @@ -# Custom Controls +# カスタムコントロール -NOTE: This guide assumes familiarity with [Signal Forms essentials](essentials/signal-forms). +NOTE: このガイドは、[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. +ブラウザの組み込みフォームコントロール(`input`、`select`、`textarea`など)は一般的なケースを扱いますが、アプリケーションではしばしば特殊な入力が必要になります。カレンダーUIを持つ日付ピッカー、書式設定ツールバーを持つリッチテキストエディタ、オートコンプリート機能を持つタグセレクターなどは、すべてカスタム実装が必要です。 -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. +シグナルフォームは、特定のインターフェースを実装するあらゆるコンポーネントと連携して動作します。**コントロールインターフェース**は、コンポーネントがフォームシステムと通信するためのプロパティとシグナルを定義します。コンポーネントがこれらのインターフェースのいずれかを実装すると、`[field]`ディレクティブが自動的にコントロールをフォームの状態、バリデーション、データバインディングに接続します。 -## Creating a basic custom control +## 基本的なカスタムコントロールの作成 {#creating-a-basic-custom-control} -Let's start with a minimal implementation and add features as needed. +最小限の実装から始めて、必要に応じて機能を追加していきましょう。 -### Minimal input control +### 最小限の入力コントロール {#minimal-input-control} -A basic custom input only needs to implement the `FormValueControl` interface and define the required `value` model signal. +基本的なカスタム入力は、`FormValueControl`インターフェースを実装し、必須の`value`モデルシグナルを定義するだけで済みます。 ```angular-ts import { Component, model } from '@angular/core'; @@ -37,12 +37,12 @@ export class BasicInput implements FormValueControl { } ``` -### Minimal checkbox control +### 最小限のチェックボックスコントロール {#minimal-checkbox-control} -A checkbox-style control needs two things: +チェックボックス形式のコントロールには、次の2つが必要です: -1. Implement the `FormCheckboxControl` interface so the `Field` directive will recognize it as a form control -2. Provide a `checked` model signal +1. `Field`ディレクティブがフォームコントロールとして認識できるように、`FormCheckboxControl`インターフェースを実装する +2. `checked`モデルシグナルを提供する ```angular-ts import { Component, model, ChangeDetectionStrategy } from '@angular/core'; @@ -71,9 +71,9 @@ export class BasicToggle implements FormCheckboxControl { } ``` -### Using your custom control +### カスタムコントロールの使用 {#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: +コントロールを作成したら、`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. +NOTE: スキーマのコールバックパラメータ(この例では`schemaPath`)は、フォーム内のすべてのフィールドへのパスを提供する`SchemaPathTree`オブジェクトです。このパラメータには好きな名前を付けることができます。 -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. +`[field]`ディレクティブは、カスタムコントロールと組み込みの入力で同じように動作します。シグナルフォームはそれらを同じように扱います - バリデーションの実行、状態の更新、データバインディングが自動的に機能します。 -## Understanding control interfaces +## コントロールインターフェースの理解 {#understanding-control-interfaces} -Now that you've seen custom controls in action, let's explore how they integrate with Signal Forms. +カスタムコントロールの動作を確認したところで、それらがシグナルフォームとどのように統合されるかを見ていきましょう。 -### Control interfaces +### コントロールインターフェース {#control-interfaces} -The `BasicInput` and `BasicToggle` components you created implement specific control interfaces that tell Signal Forms how to interact with them. +作成した`BasicInput`と`BasicToggle`コンポーネントは、シグナルフォームにそれらとの対話方法を伝える特定のコントロールインターフェースを実装しています。 -#### FormValueControl +#### FormValueControl {#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`は、テキスト入力、数値入力、日付ピッカー、セレクトドロップダウンなど、単一の値を編集するほとんどの入力タイプのためのインターフェースです。コンポーネントがこのインターフェースを実装する場合: -- **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 +- **必須プロパティ**: コンポーネントは`value`モデルシグナルを提供する必要があります +- **Fieldディレクティブの役割**: フォームフィールドの値をコントロールの`value`シグナルにバインドします -IMPORTANT: Controls implementing `FormValueControl` must NOT have a `checked` property +IMPORTANT: `FormValueControl`を実装するコントロールは`checked`プロパティを持ってはいけません -#### FormCheckboxControl +#### FormCheckboxControl {#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`は、トグル、スイッチなど、ブール値のオン/オフ状態を表すチェックボックスのようなコントロールのためのインターフェースです。コンポーネントがこのインターフェースを実装する場合: -- **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 +- **必須プロパティ**: コンポーネントは`checked`モデルシグナルを提供する必要があります +- **Fieldディレクティブの役割**: フォームフィールドの値をコントロールの`checked`シグナルにバインドします -IMPORTANT: Controls implementing `FormCheckboxControl` must NOT have a `value` property +IMPORTANT: `FormCheckboxControl`を実装するコントロールは`value`プロパティを持ってはいけません -### Optional state properties +### オプションの状態プロパティ {#optional-state-properties} -Both `FormValueControl` and `FormCheckboxControl` extend `FormUiControl` - a base interface that provides optional properties for integrating with form state. +`FormValueControl`と`FormCheckboxControl`はどちらも`FormUiControl`を拡張します。これはフォームの状態と統合するためのオプションのプロパティを提供するベースインターフェースです。 -All properties are optional. Implement only what your control needs. +すべてのプロパティはオプションです。コントロールが必要とするものだけを実装してください。 -#### Interaction state +#### インタラクションの状態 {#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 | +| `touched` | ユーザーがフィールドを操作したかどうか | +| `dirty` | 値が初期状態と異なるかどうか | -#### Validation 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 | +| `errors` | 現在のバリデーションエラーの配列 | +| `valid` | フィールドが有効かどうか | +| `invalid` | フィールドにバリデーションエラーがあるかどうか | +| `pending` | 非同期バリデーションが進行中かどうか | -#### Availability state +#### 可用性の状態 {#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 | +| `disabled` | フィールドが無効かどうか | +| `disabledReasons` | フィールドが無効になっている理由 | +| `readonly` | フィールドが読み取り専用(表示されるが編集不可)かどうか | +| `hidden` | フィールドがビューから隠されているかどうか | -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`. +NOTE: `disabledReasons`は`DisabledReason`オブジェクトの配列です。各オブジェクトは`field`プロパティ(フィールドツリーへの参照)とオプションの`message`プロパティを持ちます。メッセージには`reason.message`を介してアクセスします。 -#### Validation constraints +#### バリデーション制約 {#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 | +| `required` | フィールドが必須かどうか | +| `min` | 最小数値(制約がない場合は`undefined`) | +| `max` | 最大数値(制約がない場合は`undefined`) | +| `minLength` | 最小の文字列長(制約がない場合はundefined) | +| `maxLength` | 最大の文字列長(制約がない場合はundefined) | +| `pattern` | 一致させる正規表現パターンの配列 | -#### Field metadata +#### フィールドのメタデータ {#field-metadata} -| Property | Purpose | +| プロパティ | 目的 | | -------- | ------------------------------------------------------------------ | -| `name` | The field's name attribute (which is unique across forms and apps) | +| `name` | フィールドのname属性(フォームやアプリケーション全体で一意) | -The "[Adding state signals](#adding-state-signals)" section below shows how to implement these properties in your controls. +以下の「[状態シグナルの追加](#adding-state-signals)」セクションでは、これらのプロパティをコントロールに実装する方法を示します。 -### How the Field directive works +### Fieldディレクティブの仕組み {#how-the-field-directive-works} -The `[field]` directive detects which interface your control implements and automatically binds the appropriate signals: +`[field]`ディレクティブは、コントロールがどのインターフェースを実装しているかを検出し、適切なシグナルを自動的にバインドします: ```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/signal-forms/models). +TIP: フォームモデルの作成と管理に関する完全な情報については、[フォームモデルガイド](guide/forms/signal-forms/models)を参照してください。 -When you bind `[field]="userForm.username"`, the Field directive: +`[field]="userForm.username"`をバインドすると、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. コントロールが`FormValueControl`を実装していることを検出します +2. 内部で`userForm.username().value()`にアクセスし、それをコントロールの`value`モデルシグナルにバインドします +3. フォームの状態シグナル(`disabled()`、`errors()`など)をコントロールのオプションの入力シグナルにバインドします +4. 更新はシグナルのリアクティビティを通じて自動的に行われます -## Adding state signals +## 状態シグナルの追加 {#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'; @@ -314,7 +314,7 @@ export class StatefulInput implements FormValueControl { } ``` -As a result, you can use the control with validation and state management: +その結果、バリデーションと状態管理を備えたコントロールを使用できます: ```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. +ユーザーが無効なメールアドレスを入力すると、`Field`ディレクティブが自動的に`invalid()`と`errors()`を更新します。あなたのコントロールは、そのバリデーションフィードバックを表示できます。 -### Signal types for state properties +### 状態プロパティのシグナルタイプ {#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. +ほとんどの状態プロパティは`input()`(フォームからの読み取り専用)を使用します。コントロールがユーザーインタラクションに応じて更新する場合は、`touched`に`model()`を使用します。`touched`プロパティは、ニーズに応じて`model()`、`input()`、または`OutputRef`を一意にサポートします。 -## Value transformation +## 値の変換 {#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. +コントロールは、フォームモデルに格納されている値とは異なる形式で値を表示することがあります。例えば、日付ピッカーは「2024-01-15」と格納しながら「January 15, 2024」と表示したり、通貨入力は1234.56と格納しながら「$1,234.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/core`の`computed()`シグナルを使用してモデルの値を表示用に変換し、入力イベントを処理してユーザー入力を格納形式にパースして戻します: ```angular-ts import { Component, model, computed, ChangeDetectionStrategy } from '@angular/core'; @@ -371,10 +371,10 @@ import { FormValueControl } from '@angular/forms/signals'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CurrencyInput implements FormValueControl { - value = model(0); // Stores numeric value (1234.56) + value = model(0); // 数値 (1234.56) を格納します displayValue = computed(() => { - return this.value().toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // Shows "1,234.56" + return this.value().toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 「1,234.56」と表示します }); handleInput(input: string) { @@ -384,45 +384,45 @@ export class CurrencyInput implements FormValueControl { } ``` -## Validation integration +## バリデーションの統合 {#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). +コントロールはバリデーションの状態を表示しますが、バリデーションは実行しません。バリデーションはフォームスキーマで行われます。コントロールは`Field`ディレクティブから`invalid()`と`errors()`シグナルを受け取り、それらを表示します(上記の`StatefulInput`の例で示されているように)。 -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: +`Field`ディレクティブは、`required`、`min`、`max`、`minLength`、`maxLength`、`pattern`のようなバリデーション制約の値も渡します。コントロールはこれらを使用して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. +スキーマに`min()`と`max()`のバリデーションルールを追加すると、`Field`ディレクティブはこれらの値をコントロールに渡します。これらを使用して、HTML5属性を適用したり、テンプレートに制約のヒントを表示したりします。 -IMPORTANT: Don't implement validation logic in your control. Define validation rules in the form schema and let your control display the results: +IMPORTANT: コントロールにバリデーションロジックを実装しないでください。バリデーションルールはフォームスキーマで定義し、コントロールにはその結果を表示させるようにしてください: ```typescript -// Avoid: Validation in control +// 悪い例:コントロール内でのバリデーション export class BadControl implements FormValueControl { value = model('') - isValid() { return this.value().length >= 8 } // Don't do this! + isValid() { return this.value().length >= 8 } // これは行わないでください! } -// 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 +## 次のステップ {#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/signal-forms/models) - Creating and updating form models - - - +- [フォームモデルガイド](guide/forms/signal-forms/models) - フォームモデルの作成と更新 + + + diff --git a/adev-ja/src/content/guide/forms/signals/field-state-management.en.md b/adev-ja/src/content/guide/forms/signals/field-state-management.en.md new file mode 100644 index 0000000000..f831a4d679 --- /dev/null +++ b/adev-ja/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/signal-forms/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()) { +

Checking if email is available...

+ } + + @if (signupForm.email().invalid() && !signupForm.email().pending()) { +

Email is already taken

+ } + ` +}) +``` + +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: ` +
+ + + + @if (profileForm().dirty()) { +

You have unsaved changes

+ } +
+ ` +}) +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/signal-forms/models) - Creating models and updating values +- Validation guide - Defining validation rules and custom validators (coming soon) diff --git a/adev-ja/src/content/guide/forms/signals/field-state-management.md b/adev-ja/src/content/guide/forms/signals/field-state-management.md index f831a4d679..7c5d0caf03 100644 --- a/adev-ja/src/content/guide/forms/signals/field-state-management.md +++ b/adev-ja/src/content/guide/forms/signals/field-state-management.md @@ -1,14 +1,14 @@ -# 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`). +シグナルフォームのフィールドの状態は、バリデーションの状態(`valid`、`invalid`、`errors`など)、インタラクションの追跡(`touched`、`dirty`など)、可用性(`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`). +`form()`関数でフォームを作成すると、**フィールドツリー**が返されます。これはフォームモデルを反映したオブジェクト構造です。ツリー内の各フィールドには、ドット記法(`form.email`など)でアクセスできます。 -### Accessing field state +### フィールドの状態へのアクセス {#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: +フィールドツリー内の任意のフィールドを関数として(`form.email()`のように)呼び出すと、`FieldState`オブジェクトが返されます。これには、フィールドのバリデーション、インタラクション、および可用性の状態を追跡するリアクティブなシグナルが含まれています。たとえば、`invalid()`シグナルは、フィールドにバリデーションエラーがあるかどうかを示します: ```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. +この例では、テンプレートは`registrationForm.email().invalid()`をチェックして、エラーメッセージを表示するかどうかを判断します。 -### Field state signals +### フィールドの状態シグナル {#field-state-signals} -The most commonly used signal is `value()`, a [writable signal](guide/forms/signal-forms/models#updating-models) that provides access to the field's current value: +最も一般的に使用されるシグナルは`value()`です。これはフィールドの現在の値へのアクセスを提供する[書き込み可能なシグナル](guide/forms/signal-forms/models#updating-models)です: ```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: +`value()`に加えて、フィールドの状態には、バリデーション、インタラクションの追跡、および可用性の制御のためのシグナルが含まれています: -| 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 | +| **[バリデーション](#validation-state)** | `valid()` | フィールドがすべてのバリデーションルールに合格し、保留中のバリデーターがない | +| | `invalid()` | フィールドにバリデーションエラーがある | +| | `errors()` | バリデーションエラーオブジェクトの配列 | +| | `pending()` | 非同期バリデーションが進行中 | +| **[インタラクション](#interaction-state)** | `touched()` | ユーザーがフィールドにフォーカスし、フォーカスを外した(インタラクティブな場合) | +| | `dirty()` | ユーザーがフィールドを変更した(インタラクティブな場合)。値が初期状態と一致していても同様 | +| **[可用性](#availability-state)** | `disabled()` | フィールドが無効化されており、親フォームの状態に影響しない | +| | `hidden()` | フィールドを非表示にすることを示す。テンプレートでの可視性は`@if`で制御される | +| | `readonly()` | フィールドが読み取り専用であり、親フォームの状態に影響しない | -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} -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). +NOTE: このガイドでは、テンプレートやロジックでバリデーション状態を**使用する**こと(フィードバックを表示するために`valid()`、`invalid()`、`errors()`を読み取るなど)に焦点を当てています。バリデーションルールを**定義**したり、カスタムバリデーターを作成したりする方法については、バリデーションガイド(近日、公開予定)を参照してください。 -### Checking validity +### 有効性のチェック {#checking-validity} -Use `valid()` and `invalid()` to check validation status: +`valid()`と`invalid()`を使用してバリデーションステータスをチェックします: ```angular-ts @Component({ @@ -98,26 +98,26 @@ export class Login { } ``` -| Signal | Returns `true` when | +| シグナル | 次の場合に`true`を返します | | ----------- | --------------------------------------------------------------- | -| `valid()` | Field passes all validation rules and has no pending validators | -| `invalid()` | Field has validation errors | +| `valid()` | フィールドがすべてのバリデーションルールに合格し、保留中のバリデーターがない場合 | +| `invalid()` | フィールドにバリデーションエラーがある場合 | -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. +コードで有効性をチェックする際、「エラーがある」と「バリデーションが保留中」を区別したい場合は、`!valid()`の代わりに`invalid()`を使用してください。これは、非同期バリデーションが保留中の場合、`valid()`と`invalid()`の両方が同時に`false`になる可能性があるためです。バリデーションが完了していないためフィールドはまだ有効ではなく、またエラーがまだ見つかっていないため無効でもありません。 -### Reading validation errors +### バリデーションエラーの読み取り {#reading-validation-errors} -Access the array of validation errors with `errors()`. Each error object contains: +`errors()`でバリデーションエラーの配列にアクセスします。各エラーオブジェクトには以下が含まれます: -| 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 | +| `kind` | 失敗したバリデーションルール("required"や"email"など) | +| `message` | オプションの人間が読める形式のエラーメッセージ | +| `field` | エラーが発生した`FieldTree`への参照 | -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. +NOTE: `message`プロパティはオプションです。バリデーターはカスタムエラーメッセージを提供できますが、指定されていない場合は、エラーの`kind`値を独自メッセージにマッピングする必要があるかもしれません。 -Here's an example of how to display errors in your template: +以下は、テンプレートでエラーを表示する方法の例です: ```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. +このアプローチでは、フィールドのすべてのエラーをループ処理し、各エラーメッセージをユーザーに表示します。 -### Pending validation +### 保留中のバリデーション {#pending-validation} -The `pending()` signal indicates async validation is in progress: +`pending()`シグナルは、非同期バリデーションが進行中であることを示します: ```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. +このシグナルにより、非同期バリデーションの実行中にローディング状態を表示できます。 -## Interaction state +## インタラクション状態 {#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 +### Touched状態 {#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. +`touched()`シグナルは、ユーザーがフィールドにフォーカスし、その後ブラーしたかどうかを追跡します。ユーザー操作によって(プログラム的にではなく)フィールドにフォーカスし、その後ブラーすると`true`になります。非表示、無効、読み取り専用のフィールドは非インタラクティブであり、ユーザー操作によってtouchedになることはありません。 -### Dirty state +### Dirty状態 {#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. +フォームでは、データが実際に変更されたかどうかを検出する必要があることがよくあります。たとえば、未保存の変更についてユーザーに警告したり、必要な場合にのみ保存ボタンを有効にしたりするためです。`dirty()`シグナルは、ユーザーがフィールドを変更したかどうかを追跡します。 -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: +`dirty()`シグナルは、ユーザーがインタラクティブなフィールドの値を変更すると`true`になり、値を元の値に戻しても`true`のままです: ```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. +「未保存の変更」の警告や、データが変更された場合にのみ保存ボタンを有効にするには、`dirty()`を使用します。 -### Touched vs dirty +### Touchedとdirtyの比較 {#touched-vs-dirty} -These signals track different user interactions: +これらのシグナルは、異なるユーザーインタラクションを追跡します: -| Signal | When it becomes true | +| シグナル | 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) | +| `touched()` | ユーザーがインタラクティブなフィールドにフォーカスしてブラーしたとき(何も変更しなくても) | +| `dirty()` | ユーザーがインタラクティブなフィールドを変更したとき(一度もブラーしなくても、また現在の値が初期値と一致していても) | -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 | +| Touchedだがdirtyではない | ユーザーがフィールドにフォーカスしてブラーしたが、変更はしなかった | +| Touchedかつdirty | ユーザーがフィールドにフォーカスし、値を変更してブラーした | -NOTE: Hidden, disabled, and readonly fields are non-interactive - they don't become touched or dirty from user interactions. +NOTE: 非表示、無効、読み取り専用のフィールドは非インタラクティブです。これらはユーザー操作によってtouchedやdirtyになることはありません。 -## Availability state +## 可用性状態 {#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. +可用性状態シグナルは、フィールドがインタラクティブか、編集可能か、表示されるかを制御します。無効、非表示、読み取り専用のフィールドは非インタラクティブです。これらは、親フォームが有効か、touchedか、dirtyかには影響しません。 -### Disabled fields +### 無効なフィールド {#disabled-fields} -The `disabled()` signal indicates whether a field accepts user input. Disabled fields appear in the UI but users cannot interact with them. +`disabled()`シグナルは、フィールドがユーザー入力を受け入れるかどうかを示します。無効なフィールドはUIに表示されますが、ユーザーはそれらを操作できません。 ```angular-ts import { Component, signal } from '@angular/core' @@ -228,11 +228,11 @@ import { form, Field, disabled } from '@angular/forms/signals' selector: 'app-order', imports: [Field], template: ` - + @if (orderForm.couponCode().disabled()) { -

Coupon code is only available for orders over $50

+

クーポンコードは50ドルを超える注文でのみ利用可能です

} ` }) @@ -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. +この例では、`valueOf(schemaPath.total)`を使用して`total`フィールドの値をチェックし、`couponCode`を無効にするべきかどうかを判断します。 -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. +NOTE: スキーマのコールバックパラメータ(この例では`schemaPath`)は、フォーム内のすべてのフィールドへのパスを提供する`SchemaPathTree`オブジェクトです。このパラメータには好きな名前を付けることができます。 -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: +`disabled()`、`hidden()`、`readonly()`のようなルールを定義する場合、ロジックコールバックは通常、分割代入される(`({valueOf})`など)`FieldContext`オブジェクトを受け取ります。バリデーションルールで一般的に使用される2つのメソッドは次のとおりです: -- `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)` - フォーム内の別のフィールドの値を読み取ります +- `value()` - ルールが適用されるフィールドの値を含むシグナル -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. +無効なフィールドは、親フォームのバリデーション状態には影響しません。無効なフィールドが不正な値であっても、親フォームは有効になり得ます。`disabled()`状態はインタラクティブ性とバリデーションに影響しますが、フィールドの値は変更しません。 -### Hidden fields +### 非表示のフィールド {#hidden-fields} -The `hidden()` signal indicates whether a field is conditionally hidden. Use `hidden()` with `@if` to show or hide fields based on conditions: +`hidden()`シグナルは、フィールドが条件付きで非表示になるかどうかを示します。条件に基づいてフィールドを表示または非表示にするには、`@if`と共に`hidden()`を使用します: ```angular-ts import { Component, signal } from '@angular/core' @@ -273,12 +273,12 @@ import { form, Field, hidden } from '@angular/forms/signals' template: ` @if (!profileForm.publicUrl().hidden()) { } @@ -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. +非表示のフィールドはバリデーションに参加しません。必須フィールドが非表示の場合でも、フォームの送信は妨げられません。`hidden()`状態は可用性とバリデーションに影響しますが、フィールドの値は変更しません。 -### Readonly fields +### 読み取り専用フィールド {#readonly-fields} -The `readonly()` signal indicates whether a field is readonly. Readonly fields display their value but users cannot edit them: +`readonly()`シグナルは、フィールドが読み取り専用かどうかを示します。読み取り専用フィールドは値を表示しますが、ユーザーは編集できません: ```angular-ts import { Component, signal } from '@angular/core' @@ -311,12 +311,12 @@ import { form, Field, readonly } from '@angular/forms/signals' imports: [Field], template: ` ` @@ -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()"`. +NOTE: `[field]`ディレクティブは、フィールドの`readonly()`状態に基づいて`readonly`属性を自動的にバインドするため、手動で`[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. +無効フィールドや非表示フィールドと同様に、読み取り専用フィールドは非インタラクティブであり、親フォームの状態に影響を与えません。`readonly()`状態は編集可能性とバリデーションに影響しますが、フィールドの値は変更しません。 -### When to use each +### それぞれをいつ使用するか {#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 | +| `disabled()` | フィールドが一時的に利用できない場合(他のフィールド値に基づくなど) | はい | いいえ | いいえ | +| `hidden()` | 現在のコンテキストでフィールドが関連しない場合 | いいえ(@ifを使用) | いいえ | いいえ | +| `readonly()` | 値は表示されるべきだが、編集はできない場合 | はい | いいえ | いいえ | -## Form-level state +## フォームレベルの状態 {#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. +ルートフォームもフィールドツリー内のフィールドです。それを関数として呼び出すと、すべての子フィールドの状態を集約した`FieldState`オブジェクトも返されます。 -### Accessing form state +### フォームの状態へのアクセス {#accessing-form-state} ```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. +この例では、すべての子フィールドが有効な場合にのみフォームが有効になります。これにより、フォーム全体の有効性に基づいて送信ボタンを有効化/無効化できます。 -### Form-level signals +### フォームレベルのシグナル {#form-level-signals} -Because the root form is a field, it has the same signals (such as `valid()`, `invalid()`, `touched()`, `dirty()`, etc.). +ルートフォームはフィールドであるため、同じシグナル(`valid()`、`invalid()`、`touched()`、`dirty()`など)を持ちます。 -| 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 | +| `valid()` | すべてのインタラクティブなフィールドが有効で、保留中のバリデーターがない | +| `invalid()` | 少なくとも1つのインタラクティブなフィールドにバリデーションエラーがある | +| `pending()` | 少なくとも1つのインタラクティブなフィールドに保留中の非同期バリデーションがある | +| `touched()` | ユーザーが少なくとも1つのインタラクティブなフィールドに触れた | +| `dirty()` | ユーザーが少なくとも1つのインタラクティブなフィールドを変更した | -### When to use form-level vs field-level +### フォームレベルとフィールドレベルの使い分け {#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 +## 状態の伝播 {#state-propagation} -Field state propagates from child fields up through parent field groups to the root form. +フィールドの状態は、子フィールドから親フィールドグループを通じてルートフォームまで伝播します。 -### How child state affects parent forms +### 子の状態が親フォームに与える影響 {#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. +子フィールドが無効になると、その親フィールドグループも無効になり、ルートフォームも同様に無効になります。子がtouchedまたはdirtyになると、親フィールドグループとルートフォームはその変更を反映します。この集約により、フィールドやフォーム全体など、あらゆるレベルで有効性をチェックできます。 ```ts const userModel = signal({ @@ -420,15 +420,15 @@ const userModel = signal({ const userForm = form(userModel) -// If firstName is invalid, profile is invalid +// firstNameが無効な場合、profileも無効になります userForm.profile.firstName().invalid() === true // → userForm.profile().invalid() === true // → userForm().invalid() === true ``` -### Hidden, disabled, and readonly fields +### 非表示、無効、読み取り専用のフィールド {#hidden-disabled-and-readonly-fields} -Hidden, disabled, and readonly fields are non-interactive and don't affect parent form state: +非表示、無効、読み取り専用のフィールドは非インタラクティブであり、親フォームの状態に影響を与えません: ```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. +この例では、`shippingAddress`が非表示の場合、フォームの有効性には影響しません。その結果、`shippingAddress`が空で必須であっても、フォームは有効になり得ます。 -This behavior prevents hidden, disabled, or readonly fields from blocking form submission or affecting validation, touched, and dirty state. +この動作により、非表示、無効、または読み取り専用のフィールドがフォームの送信をブロックしたり、有効性、touched、dirtyの状態に影響を与えたりするのを防ぎます。 -## Using state in templates +## テンプレートでの状態の使用 {#using-state-in-templates} -Field state signals integrate seamlessly with Angular templates, enabling reactive form user experiences without manual event handling. +フィールド状態シグナルはAngularテンプレートとシームレスに統合され、手動のイベントハンドリングなしでリアクティブなフォームのユーザー体験を可能にします。 -### Conditional error display +### 条件付きのエラー表示 {#conditional-error-display} -Show errors only after a user has interacted with a field: +ユーザーがフィールドを操作した後にのみエラーを表示します: ```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. +このパターンは、ユーザーがフィールドを操作する機会を得る前にエラーが表示されるのを防ぎます。エラーは、ユーザーがフィールドにフォーカスしてから離れた後にのみ表示されます。 -### Conditional field availability +### 条件付きのフィールドの可用性 {#conditional-field-availability} -Use the `hidden()` signal with `@if` to show or hide fields conditionally: +`hidden()`シグナルを`@if`とともに使用して、条件付きでフィールドを表示または非表示にします: ```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. +非表示のフィールドはバリデーションに参加しないため、たとえ非表示のフィールドがそうでなければ無効であってもフォームを送信できます。 -## Using field state in component logic +## コンポーネントロジックでのフィールド状態の使用 {#using-field-state-in-component-logic} -Field state signals work with Angular's reactive primitives like `computed()` and `effect()` for advanced form logic. +フィールド状態のシグナルは、Angularのリアクティブプリミティブである`computed()`や`effect()`と連携して、高度なフォームロジックを実現します。 -### Validation checks before submission +### 送信前のバリデーションチェック {#validation-checks-before-submission} -Check form validity in component methods: +コンポーネントメソッドでフォームの有効性をチェックします: ```ts export class Registration { @@ -559,11 +559,11 @@ export class Registration { } ``` -This ensures only valid, fully-validated data reaches your API. +これにより、有効で完全にバリデーションされたデータのみがAPIに到達することが保証されます。 -### Derived state with computed +### computedによる派生状態 {#derived-state-with-computed} -Create computed signals based on field state to automatically update when the underlying field state changes: +フィールド状態に基づいてcomputedシグナルを作成すると、基礎となるフィールドの状態が変化したときに自動的に更新されます: ```ts export class Password { @@ -588,13 +588,13 @@ export class Password { } ``` -### Programmatic state changes +### プログラムによる状態変更 {#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 +#### フォームの送信 {#form-submission} -When a user submits a form, use the `submit()` function to handle validation and reveal errors: +ユーザーがフォームを送信するときは、`submit()`関数を使用してバリデーションを処理し、エラーを表示します: ```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. +`submit()`関数は、すべてのフィールドを自動的にtouchedとしてマークし(バリデーションエラーを表示)、フォームが有効な場合にのみコールバックを実行します。 -#### Resetting forms after submission +#### 送信後のフォームのリセット {#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: +フォームを正常に送信した後、ユーザーインタラクションの履歴とフィールドの値の両方をクリアして、初期状態に戻したい場合があります。`reset()`メソッドはtouchedフラグとdirtyフラグをクリアしますが、フィールドの値は変更しないため、モデルを別途更新する必要があります: ```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. +この2段階のリセットにより、古いエラーメッセージやdirty状態のインジケーターを表示することなく、フォームが新しい入力に対応できるようになります。 -## Styling based on validation state +## バリデーション状態に基づいたスタイリング {#styling-based-on-validation-state} -You can apply custom styles to your form by binding CSS classes based on the validation state: +バリデーション状態に基づいてCSSクラスをバインドすることで、フォームにカスタムスタイルを適用できます: ```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. +`touched()`とバリデーション状態の両方をチェックすることで、ユーザーがフィールドを操作した後にのみスタイルが表示されるようになります。 -## Next steps +## 次のステップ {#next-steps} -Here are other related guides on Signal Forms: +シグナルフォームに関するその他の関連ガイドは次のとおりです: -- [Form Models guide](guide/forms/signal-forms/models) - Creating models and updating values -- Validation guide - Defining validation rules and custom validators (coming soon) +- [フォームモデルガイド](guide/forms/signal-forms/models) - モデルの作成と値の更新 +- バリデーションガイド - バリデーションルールの定義とカスタムバリデーター (近日公開の予定) diff --git a/adev-ja/src/content/guide/forms/signals/models.en.md b/adev-ja/src/content/guide/forms/signals/models.en.md new file mode 100644 index 0000000000..f0f2fc4e67 --- /dev/null +++ b/adev-ja/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. + +```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: + +```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 + +```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: + +```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-ja/src/content/guide/forms/signals/models.md b/adev-ja/src/content/guide/forms/signals/models.md index f0f2fc4e67..b2db1a0733 100644 --- a/adev-ja/src/content/guide/forms/signals/models.md +++ b/adev-ja/src/content/guide/forms/signals/models.md @@ -1,18 +1,18 @@ -# 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. +NOTE: フォームモデルは、コンポーネントの双方向バインディングに使用されるAngularの`model()`シグナルとは異なります。フォームモデルはフォームデータを格納する書き込み可能なシグナルであるのに対し、`model()`は親子コンポーネント間の通信のための入力/出力を作成します。 -## What form models solve +## フォームモデルが解決すること {#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の`signal()`関数で作成される書き込み可能なシグナルです。このシグナルは、フォームのデータ構造を表すオブジェクトを保持します。 ```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). +`form()`関数はモデルのシグナルを受け取り、モデルの形状を反映した特別なオブジェクト構造である**フィールドツリー**を作成します。フィールドツリーは、ナビゲート可能(`loginForm.email`のようにドット記法で子フィールドにアクセス)であり、呼び出し可能(フィールドを関数として呼び出してその状態にアクセス)でもあります。 -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. +`[field]`ディレクティブは、各入力要素をフィールドツリー内の対応するフィールドにバインドし、UIとモデル間の自動的な双方向同期を可能にします。 -### Using TypeScript types +### TypeScriptの型を使用する {#using-typescript-types} -While TypeScript infers types from object literals, defining explicit types improves code quality and provides better IntelliSense support. +TypeScriptはオブジェクトリテラルから型を推論しますが、明示的な型を定義することでコードの品質が向上し、より良い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. +明示的な型を使用すると、フィールドツリーは完全な型安全性を提供します。`loginForm.email`へのアクセスは`FieldTree`として型付けされ、存在しないプロパティにアクセスしようとするとコンパイル時エラーが発生します。 ```ts // TypeScript knows this is FieldTree @@ -70,9 +70,9 @@ const emailField = loginForm.email const usernameField = loginForm.username ``` -### Initializing all fields +### すべてのフィールドを初期化する {#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 @@ -90,7 +90,7 @@ const userModel = signal({ }) ``` -For optional fields, explicitly set them to `null` or an empty value: +オプショナルなフィールドについては、明示的に`null`または空の値を設定してください: ```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`. +`undefined`に設定されたフィールドは、フィールドツリーから除外されます。`{value: undefined}`を持つモデルは`{}`と全く同じように動作し、そのフィールドにアクセスすると`FieldTree`ではなく`undefined`が返されます。 -### Dynamic field addition +### 動的なフィールドの追加 {#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 @@ -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. +このパターンは、ユーザーの選択やロードされたデータに基づいてフィールドが関連性を持つようになる場合に役立ちます。 -## Reading model values +## モデルの値を読み取る {#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. +フォームの値には、モデルのシグナルから直接アクセスする方法と、個々のフィールドを介してアクセスする方法の2つがあります。それぞれのアプローチは異なる目的を果たします。 -### Reading from the model +### モデルから読み取る {#reading-from-the-model} -Access the model signal when you need the complete form data, such as during form submission: +フォームの送信時など、完全なフォームデータが必要な場合は、モデルのシグナルにアクセスします: ```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. +モデルのシグナルはデータオブジェクト全体を返すため、フォームの完全な状態を扱う操作に最適です。 -### Reading from field 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. +フィールドツリー内の各フィールドは関数です。フィールドを呼び出すと、フィールドの値、バリデーションステータス、インタラクションの状態に対するリアクティブなシグナルを含む`FieldState`オブジェクトが返されます。 -Access field state when working with individual fields in templates or reactive computations: +テンプレートやリアクティブな計算で個々のフィールドを扱う場合は、フィールドの状態にアクセスします: ```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. +フィールドの状態は、各フィールドの値に対するリアクティブなシグナルを提供するため、フィールド固有の情報を表示したり、派生状態を作成したりするのに適しています。 -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). +TIP: フィールドの状態には、`value()`以外にも、バリデーションの状態(例: valid、invalid、errors)、インタラクションの追跡(例: touched、dirty)、可視性(例: hidden、disabled)など、さらに多くのシグナルが含まれています。 -## Updating form models programmatically +## フォームモデルをプログラム的に更新する {#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 +1. `set()`で[フォームモデル全体を置き換える](#replacing-form-models-with-set) +2. `update()`で[1つ以上のフィールドを更新する](#update-one-or-more-fields-with-update) +3. フィールドの状態を通じて[単一のフィールドを直接更新する](#update-a-single-field-directly-with-set) -### Replacing form models with `set()` +### `set()`でフォームモデルを置き換える {#replacing-form-models-with-set} -Use `set()` on the form model to replace the entire value: +フォームモデルで`set()`を使用して、値全体を置き換えます: ```ts loadUserData() { @@ -204,11 +204,11 @@ resetForm() { } ``` -This approach works well when loading data from an API or resetting the entire form. +このアプローチは、APIからデータを読み込む場合や、フォーム全体をリセットする場合に適しています。 -### Update one or more fields with `update()` +### `update()`で1つ以上のフィールドを更新する {#update-one-or-more-fields-with-update} -Use `update()` to modify specific fields while preserving others: +`update()`を使用して、他のフィールドを保持しながら特定のフィールドを変更します: ```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. +このパターンは、現在のモデルの状態に基づいて1つ以上のフィールドを変更する必要がある場合に便利です。 -### Update a single field directly with `set()` +### `set()`で単一のフィールドを直接更新する {#update-a-single-field-directly-with-set} -Use `set()` on individual field values to directly update the field state: +個々のフィールドの値に`set()`を使用して、フィールドの状態を直接更新します: ```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. +これらは「フィールドレベルの更新」としても知られています。これらは自動的にモデルのシグナルに伝播し、両方を同期させ続けます。 -### Example: Loading data from an API +### 例: APIからデータを読み込む {#example-loading-data-from-an-api} -A common pattern involves fetching data and populating the model: +一般的なパターンは、データを取得してモデルに投入することです: ```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. +モデルが変更されるとフォームのフィールドは自動的に更新され、追加のコードなしで取得したデータを表示します。 -## Two-way data binding +## 双方向データバインディング {#two-way-data-binding} -The `[field]` directive creates automatic two-way synchronization between the model, form state, and UI. +`[field]`ディレクティブは、モデル、フォームの状態、UIの間で自動的な双方向の同期を作成します。 -### How data flows +### データフローの仕組み {#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 +1. ユーザーが入力要素に入力する +2. `[field]`ディレクティブが変更を検知する +3. フィールドの状態が更新される +4. モデルのシグナルが更新される -**Programmatic update → UI:** +**プログラムによる更新 → 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. コードが`set()`または`update()`でモデルを更新する +2. モデルのシグナルがサブスクライバーに通知する +3. フィールドの状態が更新される +4. `[field]`ディレクティブが入力要素を更新する -This synchronization happens automatically. You don't write subscriptions or event handlers to keep the model and UI in sync. +この同期は自動的に行われます。モデルとUIを同期させるために、サブスクリプションやイベントハンドラーを記述する必要はありません。 -### Example: Both directions +### 例: 両方向 {#example-both-directions} ```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. +ユーザーが入力フィールドに入力すると、`userModel().name`が更新されます。ボタンがクリックされると、入力値は"Bob"に変わります。手動での同期コードは必要ありません。 -## Model structure patterns +## モデル構造のパターン {#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-vs-nested-models} -Flat form models keep all fields at the top level: +フラットなフォームモデルは、すべてのフィールドをトップレベルに保持します: ```ts // Flat structure @@ -333,7 +333,7 @@ const userModel = signal({ }) ``` -Nested models group related fields: +ネストされたモデルは、関連するフィールドをグループ化します: ```ts // Nested structure @@ -349,21 +349,21 @@ const userModel = signal({ }) ``` -**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 +- フィールドに明確な概念的なグループ分けがない場合 +- フィールドへのアクセスをよりシンプルにしたい場合 (`userForm.city` vs `userForm.address.city`) +- バリデーションルールが複数の潜在的なグループにまたがる場合 -**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 +- フィールドが明確な概念的なグループ(住所など)を形成する場合 +- グループ化されたデータがAPI構造と一致する場合 +- グループを1つの単位としてバリデーションしたい場合 -### Working with nested objects +### ネストされたオブジェクトの操作 {#working-with-nested-objects} -You can access nested fields by following the object path: +オブジェクトパスをたどることで、ネストされたフィールドにアクセスできます: ```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: +テンプレートでは、トップレベルのフィールドと同じ方法でネストされたフィールドをバインドします: ```ts @Component({ @@ -400,9 +400,9 @@ In templates, you bind nested fields the same way as top-level fields: }) ``` -### Working with arrays +### 配列の操作 {#working-with-arrays} -Models can include arrays for collections of items: +モデルには、アイテムのコレクションとして配列を含めることができます: ```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. +オブジェクトを含む配列のアイテムは自動的に追跡IDを受け取ります。これにより、配列内でアイテムの位置が変わってもフィールドの状態を維持できます。これにより、配列が並べ替えられた場合でも、バリデーションの状態とユーザーインタラクションが正しく維持されることが保証されます。 - + -## Model design best practices +## モデル設計のベストプラクティス {#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 +### 具体的な型を使用する {#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. +[TypeScriptの型を使用する](#using-typescript-types)で示されているように、モデルには常にインターフェースまたは型を定義してください。明示的な型は、より良いIntelliSenseを提供し、コンパイル時にエラーをキャッチし、フォームに含まれるデータに関するドキュメントとして機能します。 -### Initialize all fields +### すべてのフィールドを初期化する {#initialize-all-fields} -Provide initial values for every field in your model: +モデルのすべてのフィールドに初期値を提供してください: ```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. +初期値がない場合、それらのフィールドはフィールドツリーに存在せず、フォームのインタラクションでアクセスできなくなります。 -### Keep models focused +### モデルの焦点を絞る {#keep-models-focused} -Each model should represent a single form or a cohesive set of related data: +各モデルは、単一のフォームまたはまとまりのある関連データのセットを表すべきです: ```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. +関心事ごとにモデルを分けることで、フォームが理解しやすく、再利用しやすくなります。異なるデータセットを管理する場合は、複数のフォームを作成してください。 -### Consider validation requirements +### バリデーション要件を考慮する {#consider-validation-requirements} -Design models with validation in mind. Group fields that validate together: +バリデーションを念頭に置いてモデルを設計します。一緒にバリデーションするフィールドをグループ化してください: ```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. +この構造により、フィールド間のバリデーション(`newPassword`が`confirmPassword`と一致するかどうかのチェックなど)がより自然になります。 -### Plan for initial state +### 初期状態を計画する {#plan-for-initial-state} -Consider whether your form starts empty or pre-populated: +フォームが空の状態で始まるか、事前に入力されているかを考慮してください: ```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. +常に既存のデータで始まるフォームの場合、空のフィールドが一瞬表示されるのを避けるために、データが読み込まれるまでフォームのレンダリングを待つことができます。 + +### 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/signal-forms/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/signal-forms/models) - Creating and updating form models + + diff --git a/adev-ja/src/content/guide/forms/signals/validation.md b/adev-ja/src/content/guide/forms/signals/validation.md index aeddf0c086..5ba8f0d504 100644 --- a/adev-ja/src/content/guide/forms/signals/validation.md +++ b/adev-ja/src/content/guide/forms/signals/validation.md @@ -1,8 +1,8 @@ -# 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. +シグナルフォームは、スキーマベースのバリデーションアプローチを提供します。バリデーションルールはスキーマ関数を使用してフィールドにバインドされ、値が変更されると自動的に実行され、フィールド状態シグナルを通じてエラーを公開します。これにより、ユーザーがフォームを操作するにつれて更新されるリアクティブなバリデーションが可能になります。 @@ -10,13 +10,13 @@ Signal Forms provides a schema-based validation approach. Validation rules bind -## Validation basics +## バリデーションの基本 {#validation-basics} -Validation in Signal Forms is defined through a schema function passed as the second argument to `form()`. +シグナルフォームにおけるバリデーションは、`form()`の第二引数として渡されるスキーマ関数を通じて定義されます。 -### The schema function +### スキーマ関数 {#the-schema-function} -The schema function receives a `SchemaPathTree` object that lets you define your validation rules: +スキーマ関数は、バリデーションルールを定義するための`SchemaPathTree`オブジェクトを受け取ります: -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. +スキーマ関数はフォームの初期化中に一度だけ実行されます。バリデーションルールはスキーマパスパラメータ(`schemaPath.email`や`schemaPath.password`など)を使用してフィールドにバインドされ、フィールドの値が変更されるたびにバリデーションが自動的に実行されます。 -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. +NOTE: スキーマのコールバックパラメータ(この例では`schemaPath`)は、フォーム内のすべてのフィールドへのパスを提供する`SchemaPathTree`オブジェクトです。このパラメータには好きな名前を付けることができます。 -### How validation works +### バリデーションの仕組み {#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 +1. **スキーマでバリデーションルールを定義** - スキーマ関数内でバリデーションルールをフィールドにバインドします +2. **自動実行** - フィールドの値が変更されるとバリデーションルールが実行されます +3. **エラーの伝播** - バリデーションエラーはフィールドの状態シグナルを通じて公開されます +4. **リアクティブな更新** - バリデーションの状態が変化するとUIが自動的に更新されます -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-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 +1. **同期バリデーション** - 値が変更されると、すべての同期バリデーションルールが実行されます +2. **非同期バリデーション** - 非同期バリデーションルールは、すべての同期バリデーションルールが成功した後にのみ実行されます +3. **フィールドの状態更新** - `valid()`、`invalid()`、`errors()`、`pending()`シグナルが更新されます -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. +同期バリデーションルール(`required()`や`email()`など)は即座に完了します。非同期バリデーションルール(`validateHttp()`など)は時間がかかる場合があり、実行中は`pending()`シグナルを`true`に設定します。 -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. +すべてのバリデーションルールは変更のたびに実行されます - バリデーションは最初のエラーで中断されません。フィールドに`required()`と`email()`の両方のバリデーションルールがある場合、両方が実行され、両方が同時にエラーを生成する可能性があります。 -## Built-in validation rules +## 組み込みのバリデーションルール {#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() +### required() {#required} -The `required()` validation rule ensures a field has a value: +`required()`バリデーションルールは、フィールドに値があることを保証します: ```angular-ts import { Component, signal } from '@angular/core' @@ -96,15 +96,15 @@ export class RegistrationComponent { } ``` -A field is considered "empty" when: +フィールドは次の場合に「空」と見なされます: -| Condition | Example | +| 条件 | 例 | | ------------------------ | ------- | -| Value is `null` | `null`, | -| Value is an empty string | `''` | -| Value is an empty array | `[]` | +| 値が`null`である | `null` | +| 値が空文字列である | `''` | +| 値が空の配列である | `[]` | -For conditional requirements, use the `when` option: +条件付きの要件には、`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`. +このバリデーションルールは、`when`関数が`true`を返す場合にのみ実行されます。 -### email() +### email() {#email} -The `email()` validation rule checks for valid email format: +`email()`バリデーションルールは、有効なメール形式をチェックします: ```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`. +`email()`バリデーションルールは、標準的なメール形式の正規表現を使用します。`user@example.com`のようなアドレスは受け入れますが、`user@`や`@example.com`のような不正な形式のアドレスは拒否します。 -### min() and max() +### min()とmax() {#min-and-max} -The `min()` and `max()` validation rules work with numeric values: +`min()`と`max()`バリデーションルールは、数値に対して機能します: ```angular-ts import { Component, signal } from '@angular/core' @@ -189,7 +189,7 @@ export class AgeFormComponent { } ``` -You can use computed values for dynamic constraints: +動的な制約のために、算出値を使用できます: ```ts ageForm = form(this.ageModel, (schemaPath) => { @@ -199,9 +199,9 @@ ageForm = form(this.ageModel, (schemaPath) => { }) ``` -### minLength() and maxLength() +### minLength()とmaxLength() {#minlength-and-maxlength} -The `minLength()` and `maxLength()` validation rules work with strings and arrays: +`minLength()`と`maxLength()`バリデーションルールは、文字列と配列に対して機能します: ```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. +文字列の場合、「length」は文字数を意味します。配列の場合、「length」は要素数を意味します。 -### pattern() +### pattern() {#pattern} -The `pattern()` validation rule validates against a regular expression: +`pattern()`バリデーションルールは、正規表現に対してバリデーションを行います: ```angular-ts import { Component, signal } from '@angular/core' @@ -284,37 +284,37 @@ export class PhoneFormComponent { } ``` -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 | +| 電話番号 | `/^\d{3}-\d{3}-\d{4}$/` | 555-123-4567 | +| 郵便番号 (米国) | `/^\d{5}$/` | 12345 | +| 英数字 | `/^[a-zA-Z0-9]+$/` | abc123 | +| URLセーフ | `/^[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. +バリデーションルールが失敗すると、何が問題だったかを説明するエラーオブジェクトが生成されます。エラーの構造を理解することは、ユーザーに明確なフィードバックを提供するのに役立ちます。 - +NOTE: このセクションでは、バリデーションルールが生成するエラーについて説明します。UIでバリデーションエラーを表示して使用する方法については、[フィールド状態管理ガイド](guide/forms/signal-forms/field-state-management)を参照してください。 --> -### Error structure +### エラーの構造 {#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 | +| `kind` | 失敗したバリデーションルール(例: "required", "email", "minLength") | +| `message` | オプションの人間が読める形式のエラーメッセージ | -Built-in validation rules automatically set the `kind` property. The `message` property is optional - you can provide custom messages through validation rule options. +組み込みのバリデーションルールは、自動的に`kind`プロパティを設定します。`message`プロパティはオプションで、バリデーションルールのオプションを通じてカスタムメッセージを提供できます。 -### Custom error messages +### カスタムエラーメッセージ {#custom-error-messages} -All built-in validation rules accept a `message` option for custom error text: +すべての組み込みバリデーションルールは、カスタムエラーテキストのために`message`オプションを受け入れます: ```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". +カスタムメッセージは、明確で具体的であり、ユーザーに問題の修正方法を伝えるべきです。「無効な入力」の代わりに、「セキュリティのため、パスワードは12文字以上である必要があります」のようにします。 -### Multiple errors per field +### フィールドごとの複数のエラー {#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) => { @@ -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. +emailフィールドが空の場合、`required()`エラーのみが表示されます。ユーザーが"a@b"と入力すると、`email()`と`minLength()`の両方のエラーが表示されます。すべてのバリデーションルールが実行され、最初の失敗でバリデーションが停止することはありません。 -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/signal-forms/field-state-management#conditional-error-display). +TIP: テンプレートで`touched() && invalid()`パターンを使用すると、ユーザーがフィールドを操作する前にエラーが表示されるのを防ぐことができます。バリデーションエラーの表示に関する包括的なガイダンスについては、[フィールド状態管理ガイド](guide/forms/signal-forms/field-state-management#conditional-error-display)を参照してください。 -## Custom validation rules +## カスタムバリデーションルール {#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() +### validate()の使用 {#using-validate} -The `validate()` function creates custom validation rules. It receives a validator function that accesses the field context and returns: +`validate()`関数はカスタムバリデーションルールを作成します。これは、フィールドコンテキストにアクセスし、以下の値を返すバリデーター関数を受け取ります: -| Return Value | Meaning | +| 戻り値 | 意味 | | --------------------- | ---------------- | -| Error object | Value is invalid | -| `null` or `undefined` | Value is valid | +| エラーオブジェクト | 値は無効です | +| `null` または `undefined` | 値は有効です | ```angular-ts import { Component, signal } from '@angular/core' @@ -423,25 +423,25 @@ export class UrlFormComponent { } ``` -The validator function receives a `FieldContext` object with: +バリデーター関数は、以下のプロパティを持つ`FieldContext`オブジェクトを受け取ります: -| 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 | +| `value` | Signal | 現在のフィールド値を含むSignal | +| `state` | FieldState | フィールドの状態への参照 | +| `field` | FieldTree | フィールドツリーへの参照 | +| `valueOf()` | Method | パスで指定された他のフィールドの値を取得します | +| `stateOf()` | Method | パスで指定された他のフィールドの状態を取得します | +| `fieldTreeOf()` | Method | パスで指定された他のフィールドのフィールドツリーを取得します | +| `pathKeys` | Signal | ルートから現在のフィールドまでのパスキー | -NOTE: Child fields also have a `key` signal, and array item fields have both `key` and `index` signals. +NOTE: 子フィールドには`key`シグナルもあり、配列アイテムのフィールドには`key`と`index`の両方のシグナルがあります。 -Return an error object with `kind` and `message` when validation fails. Return `null` or `undefined` when validation passes. +バリデーションが失敗した場合は`kind`と`message`を持つエラーオブジェクトを返します。バリデーションが成功した場合は`null`または`undefined`を返します。 -### Reusable validation rules +### 再利用可能なバリデーションルール {#reusable-validation-rules} -Create reusable validation rule functions by wrapping `validate()`: +`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: +カスタムバリデーションルールは、組み込みのバリデーションルールと同じように使用できます: ```ts urlForm = form(this.urlModel, (schemaPath) => { @@ -483,11 +483,11 @@ urlForm = form(this.urlModel, (schemaPath) => { }) ``` -## Cross-field validation +## クロスフィールドバリデーション {#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' @@ -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. +確認用のバリデーションルールは`valueOf(schemaPath.password)`を使用してパスワードフィールドの値にアクセスし、確認用の値と比較します。このバリデーションルールはリアクティブに実行されます - どちらかのパスワードが変更されると、バリデーションが自動的に再実行されます。 -## Async validation +## 非同期バリデーション {#async-validation} -Async validation handles validation that requires external data sources, like checking username availability on a server or validating against an API. +非同期バリデーションは、サーバーでのユーザー名の利用可能性のチェックやAPIに対するバリデーションなど、外部データソースを必要とするバリデーションを処理します。 -### Using validateHttp() +### validateHttp()の使い方 {#using-validatehttp} -The `validateHttp()` function performs HTTP-based validation: +`validateHttp()`関数は、HTTPベースのバリデーションを実行します: ```angular-ts import { Component, signal, inject } from '@angular/core' @@ -600,17 +600,17 @@ export class UsernameFormComponent { } ``` -The `validateHttp()` validation rule: +`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. `request`関数によって返されるURLまたはリクエストを呼び出します +2. `onSuccess`を使用して、成功レスポンスをバリデーションエラーまたは`null`にマッピングします +3. `onError`を使用して、リクエストの失敗(ネットワークエラー、HTTPエラー)を処理します +4. リクエストが進行中の間、`pending()`を`true`に設定します +5. すべての同期バリデーションルールが成功した後にのみ実行されます -### Pending state +### ペンディング状態 {#pending-state} -While async validation runs, the field's `pending()` signal returns `true`. Use this to show loading indicators: +非同期バリデーションの実行中、フィールドの`pending()`シグナルは`true`を返します。これを使用してローディングインジケーターを表示します: ```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. +`valid()`シグナルは、まだエラーがない場合でも、バリデーションがペンディング中の間は`false`を返します。`invalid()`シグナルは、エラーが存在する場合にのみ`true`を返します。 -## Next steps +## 次のステップ {#next-steps} -This guide covered creating and applying validation rules. Related guides explore other aspects of Signal Forms: +このガイドでは、バリデーションルールの作成と適用について説明しました。関連ガイドでは、シグナルフォームの他の側面について解説します: -- [Form Models guide](guide/forms/signal-forms/models) - Creating and updating form models +- [フォームモデルガイド](guide/forms/signal-forms/models) - フォームモデルの作成と更新