###### 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
) {}
}
```