###### tags: `Guide` `Form` `Angular` # 🌝Angular基礎教學04 #表單 **主要內容如下** 1. 認識表單及應用 **表單基本介紹** [Angular文件-Form](https://angular.tw/guide/forms-overview) 表單常見的應用為使用者輸入的處理(ex: 登入),Angular提供兩種表單(Form),兩者皆提供: 1. 捕捉使用者的輸入事件 2. 驗證使用者的輸入 3. 建立表單模型 4. 修改資料模型 5. 提供追蹤這些更改的途徑 兩者皆有各自的優缺點,在建立、資料流、測試等方面皆不同,可依照需求選擇適合的表單使用。 ## Template-driven-forms (範本驅動表單) ==*範例*==[StackBlitz-Template-driven-forms](https://stackblitz.com/edit/template-driven-forms-demo?file=src/app/app.component.ts) ### Template-driven-forms特性 - 簡易型,適合基本表單需求 - 優點: 容易性 - 表單驗證: 指令(Directive) ### Template-driven-forms範例 #### #Example1. 表單提交/驗證 *step1.* 加入FormsModule ==app.module.ts== ```typescript imports: [ BrowserModule, AppRoutingModule, FormsModule // <-- 加入FormsModule ], ``` *step2.* 準備ngModel綁定的參數,及提交事件 ==app.component.ts== ```typescript export class AppComponent { account = ''; password = ''; submit() { console.log('ruby submit!'); } } ``` *step3.* 建立form並使用表單的驗證 ==app.component.html== ```htmlmixed <form (ngSubmit)="userForm.valid && submit()" #userForm="ngForm"> <div> <label>Name: <input type="text" name="name" #name="ngModel" [(ngModel)]="user.account" required="required" minlength="3"> </label> <span style="color:red;" *ngIf="name.invalid && (name.dirty || name.touched)">必填</span> </div> <br> <div> <label>Password: <input type="password" name="password" #password="ngModel" [(ngModel)]="user.password" required="required" minlength="5"> </label> <span style="color:red;" *ngIf="password.invalid && (password.dirty || password.touched)">必填</span> </div> <br> <div> <button type="submit">Submit</button> </div> </form> ``` ### Template-driven-forms錯誤集 #### #Error1. `⚡If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions. ` 此錯誤意思是在`<form>`內有使用ngModel的標籤內都應該加上name標籤或是設定ngModelOptions為standalone。 ``` Example 1: <input [(ngModel)]="person.firstName" name="first"> Example 2: <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}"> ``` 在這邊我們加上name屬性 ```htmlmixed <form> <div> <label>帳號: </label> <input type="text" name="account" [(ngModel)]="account" > </div> <div> <label>密碼: </label> <input type="text" name="password" [(ngModel)]="password" > </div> </form> ``` ## Reactive-forms (響應式表單) [Angular-Reactive-forms](https://angular.tw/guide/reactive-forms) ==*範例*==[StackBlitz-Reactive-driven-forms](https://stackblitz.com/edit/reactive-form-demo-ru?file=src/app/app.component.html) ### Reactive-forms 特性 - 複雜型,適合表單為應用程式的重要部分 - 優點: 可擴充套件性、可複用性、可測試性 - 表單驗證: 函式 ### Reactive-forms範例 #### #Example1. 基礎表單控制元件 *step1.* 加入ReactiveFormsModule ==app.module.ts== ```typescript imports: [ BrowserModule, AppRoutingModule, ReactiveFormsModule // <-- 加入ReactiveFormsModule ], ``` *step2.* 新增FormControl,並準備控制事件 ==app.component.ts== ```typescript export class AppComponent { example1 = {id: new FormControl(''), name: new FormControl('')}; onExample1() { // console.log(this.user); console.log('ruby Example1', this.example1); this.example1.name.setValue(this.example1.id.value); } } ``` *step3.* 在範本中使用FormControl ==app.component.html== ```htmlembedded <div> <label>Example1.</label> <div> <label>Input: <input type="text" [formControl]="this.example1.id"> <input type="text" [formControl]="this.example1.name" readonly> </label> <button type="submit" (click)="onExample1()">Copy</button> </div> </div> ``` :::success 📝小補充 在Example1.中,只使用簡單的單個控制元件來做示範,但若是呼叫FormGroup、FormArray實例的setValue(),必須注意符合元件組或控制元件陣列的結構。 ::: #### #Example2. 控制元件分組 表單常會有需要互相關聯的控制元件,Reactice-forms提供兩種方式來分組。 1. FormGroup 2. FormArray 此範例使用FormGroup。 *step1.* 建立FormGroup與設定FormControl驗證事件 ==app.component.ts== ```typescript export class AppComponent { example2 = new FormGroup({ account: new FormControl('', Validators.required), password: new FormControl('', Validators.required) }) onExample2() { console.log('ruby Example2', this.example2); } } ``` *step2.* 在範本中使用formControlName關聯FormGroup模型 ==app.component.html== ```htmlmixed <form [formGroup]="example2" (ngSubmit)="onExample2()"> <label>Example2.</label> <div> <label>Account: <input type="text" formControlName="account"> </label> </div> <div> <label>Password: <input type="password" formControlName="password"> </label> </div> <button type="submit" [disabled]="!example2.valid">Submit</button> </form> ``` :::success 📝小補充 * Example2. step1. `...new FormControl('', Validators.required),...` Validators.required是驗證器,可以依自己的需求自製驗證器加上。 * Example2. step2. `[disabled]="!example2.valid"` 意思是需要example2中的FormControl驗證項目皆正確才能使用按鈕。 ::: *step3.* 建立巢狀表單模組 表單組除了接受單個控制元件,也能接受將其他表單組作為子控制元件,地址為巢狀表單的典型例子 ==app.component.ts== ```typescript export class AppComponent { example2 = new FormGroup({ account: new FormControl('', Validators.required), password: new FormControl('', Validators.required), address: new FormGroup({ city: new FormControl(''), street: new FormControl(''), }) }) onExample2() { console.log('ruby Example2', this.example2); } } ``` ==app.component.html== ```htmlmixed <form [formGroup]="example2" (ngSubmit)="onExample2()"> <label>Example2.</label> <div> <label>Account: <input type="text" formControlName="account"> </label> </div> <div> <label>Password: <input type="password" formControlName="password"> </label> </div> <div formGroupName="address"> <label>Address: <input type="text" formControlName="city"> <input type="text" formControlName="street"> </label> </div> <button type="submit" [disabled]="!example2.valid">Submit</button> </form> ``` *step4.* 更新資料 ==app.component.ts== ```typescript export class AppComponent { example2 = new FormGroup({ account: new FormControl('', Validators.required), password: new FormControl('', Validators.required), address: new FormGroup({ city: new FormControl(''), street: new FormControl(''), }) }) onExample2() { console.log('ruby Example2', this.example2); } onExample2_(type: number) { if (type == 1) { // setValue this.example2.get('account').setValue('setValue'); this.example2.get('address').setValue({city: 'city', street: 'street'}); } if (type == 2) { // patchValue this.example2.patchValue({ account: 'patchValue', address: {city: 'city', street: 'street'} }) } } } ``` ==app.component.html== ```htmlmixed <form [formGroup]="example2" (ngSubmit)="onExample2()"> <label>Example2.</label> <div> <label>Account: <input type="text" formControlName="account"> </label> </div> <div> <label>Password: <input type="password" formControlName="password"> </label> </div> <div formGroupName="address"> <label>Address: <input type="text" formControlName="city"> <input type="text" formControlName="street"> </label> </div> <button type="submit" [disabled]="!example2.valid">Submit</button> <button type="submit" (click)="onExample2_(1)">SetValue</button> <button type="submit" (click)="onExample2_(2)">PatchValue</button> </form> ``` :::success 📝小補充 有兩種更新模型值的方式 1. setValue(), 為單個控制元件設定新值,嚴格遵守表單組結構,嚴格的檢查可以協助我們捕捉錯誤。 2. patchValue(),可以用物件中所定義的任何屬性為表單模型進行替換,但遇到錯誤時會默默的失敗。 ::: #### #Example3. 使用 FormBuilder 服務產生控制元件 當需要多個表單時,手動建立多個控制元件會變得很繁瑣,FormBuilder提供一些方便的方法可以使用 1. control() => FormControl 2. group() => FormGroup 3. array() => FormArray 此範例使用group()。 *step1.* 注入FormBuilder ==app.component.ts== ```typescript constructor(private fb: FormBuilder) { } ``` *step2.* 使用FormBuilder服務提供的方法產生控制元件 ==app.component.ts== ```typescript export class AppComponent { example3 = this.fb.group({ account: ['', Validators.required], password: ['', Validators.required], address: this.fb.group({ city: [''], street: [''], }), }) constructor( private fb: FormBuilder ) {} } ```