在 Angular 的世界中,無論您是在製作簡單的登入頁面還是更複雜的使用者設定檔介面,表單對於使用者互動都是至關重要的。 Angular 傳統上提供兩種主要方法:範本驅動表單和反應式表單。在我之前的 Angular 反應式表單系列中,我探索如何利用反應式表單的強大功能來管理複雜邏輯、建立動態表單以及建立自訂表單控制項。
用於管理反應性的新工具 - 訊號 - 已在 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>
以上是探索角度形式:訊號的新替代方案的詳細內容。更多資訊請關注PHP中文網其他相關文章!