ホームページ >ウェブフロントエンド >jsチュートリアル >Angular フォームの探索: シグナルを使用した新しい選択肢
Angular の世界では、単純なログイン ページを作成する場合でも、より複雑なユーザー プロファイル インターフェイスを作成する場合でも、フォームはユーザーとの対話に不可欠です。 Angular は伝統的に、テンプレート駆動型フォーム と リアクティブ フォーム という 2 つの主要なアプローチを提供しています。 Angular Reactive Forms に関する以前のシリーズでは、リアクティブ フォームの機能を活用して複雑なロジックを管理し、動的フォームを作成し、カスタム フォーム コントロールを構築する方法を検討しました。
反応性を管理するための新しいツールである シグナル は、Angular のバージョン 16 で導入され、それ以来 Angular メンテナーの注目を集めており、バージョン 17 で安定しました。シグナルを使用すると、状態の変更を処理できます。宣言的に、テンプレート駆動型のシンプルさとリアクティブ型の堅牢な反応性を組み合わせたエキサイティングな代替手段を提供します。この記事では、Angular の単純なフォームと複雑なフォームの両方にシグナルがどのように反応性を追加できるかを検証します。
シグナルを使用してテンプレート駆動のフォームを強化するというトピックに入る前に、Angular の従来のフォームのアプローチを簡単にまとめてみましょう。
テンプレート駆動フォーム: ngModel などのディレクティブを使用して HTML テンプレートで直接定義されるこれらのフォームは、セットアップが簡単で、単純なフォームに最適です。ただし、より複雑なシナリオに必要なきめ細かい制御が提供されない場合があります。
テンプレート駆動型フォームの最小限の例を次に示します:
<form (ngSubmit)="onSubmit()"> <label for="name">Name:</label> <input> </li> </ol> <pre class="brush:php;toolbar:false">```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent { name = ''; onSubmit() { console.log(this.name); } } ```
リアクティブ フォーム: Angular の FormGroup、FormControl、FormArray クラスを使用して、コンポーネント クラスでプログラムによって管理されます。リアクティブ フォームでは、フォームの状態と検証を詳細に制御できます。 Angular Reactive Forms に関する以前の記事で説明したように、このアプローチは複雑なフォームに適しています。
そして、これがリアクティブフォームの最小限の例です:
import { Component } from '@angular/core'; import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent { form = new FormGroup({ name: new FormControl('') }); onSubmit() { console.log(this.form.value); } }
```html <form [formGroup]="form" (ngSubmit)="onSubmit()"> <label for="name">Name:</label> <input> <h2> Introducing Signals as a New Way to Handle Form Reactivity </h2> <p>With the release of Angular 16, signals have emerged as a new way to manage reactivity. Signals provide a declarative approach to state management, making your code more predictable and easier to understand. When applied to forms, signals can enhance the simplicity of template-driven forms while offering the reactivity and control typically associated with reactive forms.</p> <p>Let’s explore how signals can be used in both simple and complex form scenarios.</p> <h3> Example 1: A Simple Template-Driven Form with Signals </h3> <p>Consider a basic login form. Typically, this would be implemented using template-driven forms like this:<br> </p> <pre class="brush:php;toolbar:false"><!-- login.component.html --> <form name="form" (ngSubmit)="onSubmit()"> <label for="email">E-mail</label> <input type="email"> <pre class="brush:php;toolbar:false">// login.component.ts import { Component } from "@angular/core"; @Component({ selector: "app-login", templateUrl: "./login.component.html", }) export class LoginComponent { public email: string = ""; public password: string = ""; onSubmit() { console.log("Form submitted", { email: this.email, password: this.password }); } }
このアプローチは単純なフォームにはうまく機能しますが、シグナルを導入することで、シンプルさを維持しながらリアクティブ機能を追加できます。
// login.component.ts import { Component, computed, signal } from "@angular/core"; import { FormsModule } from "@angular/forms"; @Component({ selector: "app-login", standalone: true, templateUrl: "./login.component.html", imports: [FormsModule], }) export class LoginComponent { // Define signals for form fields public email = signal(""); public password = signal(""); // Define a computed signal for the form value public formValue = computed(() => { return { email: this.email(), password: this.password(), }; }); public isFormValid = computed(() => { return this.email().length > 0 && this.password().length > 0; }); onSubmit() { console.log("Form submitted", this.formValue()); } }
<!-- login.component.html --> <form name="form" (ngSubmit)="onSubmit()"> <label for="email">E-mail</label> <input type="email"> <p>In this example, the form fields are defined as signals, allowing for reactive updates whenever the form state changes. The formValue signal provides a computed value that reflects the current state of the form. This approach offers a more declarative way to manage form state and reactivity, combining the simplicity of template-driven forms with the power of signals.</p> <p>You may be tempted to define the form directly as an object inside a signal. While such an approach may seem more concise, typing into the individual fields does not dispatch reactivity updates, which is usually a deal breaker. Here’s an example StackBlitz with a component suffering from such an issue:</p> <p>Therefore, if you'd like to react to changes in the form fields, it's better to define each field as a separate signal. By defining each form field as a separate signal, you ensure that changes to individual fields trigger reactivity updates correctly. </p> <h3> Example 2: A Complex Form with Signals </h3> <p>You may see little benefit in using signals for simple forms like the login form above, but they truly shine when handling more complex forms. Let's explore a more intricate scenario - a user profile form that includes fields like firstName, lastName, email, phoneNumbers, and address. The phoneNumbers field is dynamic, allowing users to add or remove phone numbers as needed.</p> <p>Here's how this form might be defined using signals:<br> </p> <pre class="brush:php;toolbar:false">// user-profile.component.ts import { JsonPipe } from "@angular/common"; import { Component, computed, signal } from "@angular/core"; import { FormsModule, Validators } from "@angular/forms"; @Component({ standalone: true, selector: "app-user-profile", templateUrl: "./user-profile.component.html", styleUrls: ["./user-profile.component.scss"], imports: [FormsModule, JsonPipe], }) export class UserProfileComponent { public firstName = signal(""); public lastName = signal(""); public email = signal(""); // We need to use a signal for the phone numbers, so we get reactivity when typing in the input fields public phoneNumbers = signal([signal("")]); public street = signal(""); public city = signal(""); public state = signal(""); public zip = signal(""); public formValue = computed(() => { return { firstName: this.firstName(), lastName: this.lastName(), email: this.email(), // We need to do a little mapping here, so we get the actual value for the phone numbers phoneNumbers: this.phoneNumbers().map((phoneNumber) => phoneNumber()), address: { street: this.street(), city: this.city(), state: this.state(), zip: this.zip(), }, }; }); public formValid = computed(() => { const { firstName, lastName, email, phoneNumbers, address } = this.formValue(); // Regex taken from the Angular email validator const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; const isEmailFormatValid = EMAIL_REGEXP.test(email); return ( firstName.length > 0 && lastName.length > 0 && email.length > 0 && isEmailFormatValid && phoneNumbers.length > 0 && // Check if all phone numbers are valid phoneNumbers.every((phoneNumber) => phoneNumber.length > 0) && address.street.length > 0 && address.city.length > 0 && address.state.length > 0 && address.zip.length > 0 ); }); addPhoneNumber() { this.phoneNumbers.update((phoneNumbers) => { phoneNumbers.push(signal("")); return [...phoneNumbers]; }); } removePhoneNumber(index: number) { this.phoneNumbers.update((phoneNumbers) => { phoneNumbers.splice(index, 1); return [...phoneNumbers]; }); } }
phoneNumbers フィールドが信号配列の信号として定義されていることに注意してください。この構造により、個々の電話番号の変更を追跡し、フォームの状態を事後的に更新できます。 addPhoneNumber メソッドとremovePhoneNumber メソッドは、phoneNumbers 信号配列を更新し、フォーム内の反応性の更新をトリガーします。
<!-- user-profile.component.html --> <blockquote> <p>テンプレートでは、phoneNumbers 信号配列を使用して電話番号入力フィールドを動的にレンダリングします。 addPhoneNumber メソッドとremovePhoneNumber メソッドを使用すると、ユーザーは電話番号をリアクティブに追加または削除し、フォームの状態を更新できます。 track 関数の使用法に注目してください。これは、ngFor ディレクティブがphoneNumbers 配列への変更を正しく追跡するために必要です。</p> </blockquote> <p>これは、試してみたい複雑なフォームの例の StackBlitz デモです:</p> <h3> シグナルによるフォームの検証 </h3> <p>検証はどのフォームにとっても重要であり、ユーザー入力が送信前に必要な基準を満たしていることを確認します。シグナルを使用すると、検証は事後的かつ宣言的な方法で処理できます。上記の複雑なフォームの例では、すべてのフィールドが特定の検証基準を満たしているかどうかをチェックする、formValid という計算信号を実装しました。</p> <p>検証ロジックは、有効な電子メール形式をチェックしたり、すべての必須フィールドが入力されていることを確認したりするなど、さまざまなルールに合わせて簡単にカスタマイズできます。検証にシグナルを使用すると、検証ルールが明確に定義され、フォーム フィールドの変更に自動的に反応するため、より保守しやすくテストしやすいコードを作成できます。別のユーティリティに抽象化して、さまざまな形式で再利用できるようにすることもできます。</p> <p>複雑なフォームの例では、formValid 信号により、すべての必須フィールドが入力されていることを確認し、電子メールと電話番号の形式が検証されます。</p> <p>この検証のアプローチは少し単純なので、実際のフォームフィールドとより適切に接続する必要があります。多くのユースケースで機能しますが、場合によっては、明示的な「シグナルフォーム」サポートが Angular に追加されるまで待ったほうがよい場合もあります。 Tim Deschryver は、検証を含む信号形式に関するいくつかの抽象化の実装を開始し、それに関する記事を書きました。将来、このようなものが Angular に追加されるかどうか見てみましょう。</p> <h3> 信号を角のある形式で使用する理由 </h3> <p>Angular でのシグナルの採用により、フォームの状態と反応性を管理するための強力な新しい方法が提供されます。シグナルは、テンプレート駆動型フォームとリアクティブ フォームの長所を組み合わせることで、複雑なフォーム処理を簡素化できる柔軟な宣言型アプローチを提供します。 Angular 形式でシグナルを使用する主な利点をいくつか示します:</p> <ol> <li><p><strong>宣言的な状態管理</strong>: シグナルを使用すると、フォーム フィールドと計算値を宣言的に定義できるため、コードがより予測可能になり、理解しやすくなります。</p></li> <li><p><strong>反応性</strong>: シグナルはフォーム フィールドに反応的な更新を提供し、フォームの状態の変更によって反応性の更新が自動的にトリガーされるようにします。</p></li> <li><p><strong>粒度の高い制御</strong>: シグナルを使用すると、フォーム フィールドを粒度の高いレベルで定義でき、フォームの状態と検証をきめ細かく制御できます。</p></li> <li><p><strong>動的フォーム</strong>: シグナルを使用すると、動的に追加または削除できるフィールドを持つ動的フォームを作成でき、複雑なフォーム シナリオを処理する柔軟な方法を提供します。</p></li> <li><p><strong>シンプルさ</strong>: シグナルは、従来のリアクティブ フォームよりもシンプルで簡潔なフォームの状態管理方法を提供し、複雑なフォームの構築と維持を容易にします。</p></li> </ol> <h3> 結論 </h3> <p>前回の記事では、動的なフォームの構築からカスタム フォーム コントロールに至るまで、Angular リアクティブ フォームの強力な機能について説明しました。シグナルの導入により、Angular 開発者は、テンプレート駆動型フォームのシンプルさとリアクティブ フォームの反応性を統合する新しいツールを利用できるようになりました。</p> <p>多くのユースケースではリアクティブ フォームが正当化されますが、シグナルは、より直接的で宣言的なアプローチを必要とする Angular アプリケーションでフォームの状態を管理するための新鮮で強力な代替手段を提供します。 Angular は進化し続けるため、これらの新機能を試してみることは、より保守しやすく、パフォーマンスの高いアプリケーションを構築するのに役立ちます。</p> <p>コーディングを楽しんでください!</p>
以上がAngular フォームの探索: シグナルを使用した新しい選択肢の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。