Angular 深入淺出三十天:表單與測試 Day29 - ControlContainer

Day29

昨天跟大家分享了自訂表單元件的作法,但昨天的作法只適用於一個欄位、一個 FormControl

雖然 FormControl 裡是可以設 {} 的值,但如果我們真的想要的是一個可以直接用 [formGroup][formArray] 所使用的元件呢?

沒問題,只要你想要, Angular 都給你

實作開始

大家還記得之前我們做了個「被保人表單」吧?

一開始只有「姓名」、「性別」跟「年齡」這三個欄位,後來我們加了「聯絡資訊」的欄位,這次我們再幫它加個「聯絡地址」的欄位吧!

一般來說,聯絡地址的欄位通常會分成「縣市」、「鄉鎮市區」、「郵遞區號」與「地址」,而「縣市」、「鄉鎮市區」與「郵遞區號」之間會有一些連動邏輯,縣市」與「鄉鎮市區」這兩個欄位也通常會是下拉選單,其他的則是一般的 input 欄位。

首先一樣先把 HTML 準備好,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
<p><label>聯絡地址:</label></p>
<p>
<select>
<option value="">請選擇縣市</option>
</select>
<select>
<option value="">請選擇鄉鎮市區</option>
</select>
</p>
<p>
<input type="text" style="width: 4rem" placeholder="郵遞區號">
<input type="text" place="請輸入地址">
</p>

畫面:

Template View

樣式用 inline 的方式設定是方便教學,小朋友們要盡量少用噢!

接著,在 .ts 裡加入地址的相關欄位的 FormGroupFormControl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const addressInfoFormGroup = this.formBuilder.group({
city: '',
district: '',
zip: '',
address: ''
});

return this.formBuilder.group({
name: [
'',
[Validators.required, Validators.minLength(2), Validators.maxLength(10)]
],
gender: ['', Validators.required],
age: ['', Validators.required],
contactInfoType: contactInfoTypeControl,
contactInfo: contactInfoControl,
addressInfo: addressInfoFormGroup
});

然後再將其綁與畫面上元素綁定,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ng-container formGroupName="addressInfo">
<p><label>聯絡地址:</label></p>
<p>
<select formControlName="city">
<option value="">請選擇縣市</option>
</select>
<select formControlName="district">
<option value="">請選擇鄉鎮市區</option>
</select>
</p>
<p>
<input type="text" style="width: 4rem" placeholder="郵遞區號" formControlName="zip">
<input type="text" placeholder="請輸入地址" formControlName="address">
</p>
</ng-container>

連動邏輯的實作就交給大家練習囉,我們今天沒有要著重於此部分的處理。

至此,我們就完成了第一步的準備工作。

ControlContainer

接下來,我們就要將聯絡地址這塊拆成一個獨立的 Component ─ AddressInfoComponent

首先,先將 HTML 搬過去並稍微調整一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ng-container [formGroup]="formGroup">
<p>
<select formControlName="city">
<option value="">請選擇縣市</option>
</select>
<select formControlName="district">
<option value="">請選擇鄉鎮市區</option>
</select>
</p>
<p>
<input type="text" style="width: 4rem" placeholder="郵遞區號" formControlName="zip">
<input type="text" placeholder="請輸入地址" formControlName="address">
</p>
</ng-container>

接著在 AddressInfoComponent 裡注入 ControlContainer

1
2
3
4
5
export class AddressInfoComponent {

constructor(private controlContainer: ControlContainer) { }

}

然後加上:

1
2
3
get formGroup(): FormGroup {
return this.controlContainer.control as FormGroup;
}

再回到被保人表單裡,把原本的聯絡地址區塊改成:

1
2
<p><label>聯絡地址:</label></p>
<app-address-info formGroupName="addressInfo"></app-address-info>

至此就大功告成了!是不是超簡單的?!

不過之所以這麼簡單是因為這是 Reactive Forms 的方式,今天的 ControlContainer 不像昨天的 ControlValueAccessor 可以做一次之後,兩種方式都可以使用。

如果今天這個元件是要讓 Template Driven Forms 使用的話,首先要先將 Template 原本用 Reactive Forms 的綁定方式改成使用 Template Driven Forms 的綁定方式,像是這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ng-container ngModelGroup="addressInfo">
<p>
<select name="zip" ngModel>
<option value="">請選擇縣市</option>
</select>
<select name="district" >
<option value="">請選擇鄉鎮市區</option>
</select>
</p>
<p>
<input type="text" style="width: 4rem" placeholder="郵遞區號" name="zip" ngModel>
<input type="text" placeholder="請輸入地址" name="address" ngModel>
</p>
</ng-container>

然後也不用在 AddressInfoComponent 裡注入 ControlContainer ,而是改在 AddressInfoComponentMetaDataviewProviders 裡新增以下設定:

1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector: 'app-address-info',
templateUrl: './address-info.component.html',
styleUrls: ['./address-info.component.scss'],
viewProviders:[
{
provide: ControlContainer,
useExisting: NgForm
}
]
})
export class AddressInfoComponent {

這樣就能直接用 <app-address-info></app-address-info> 的方式使用這個元件了。

大家覺得,是 Reactive Forms 的方式好用,還是 Template Driven Forms 的方式好用呢?

本日小結

今天的重點主要是讓大家知道要怎麼使用 ControlContainer 這個類別來包裝我們的元件,以達到提昇重用性維護性的目的。

雖然麻煩的是,它沒辦法像昨天分享的 ControlValueAccessor 一樣,做好了之後可以適用於 Template Driven FormsReactive Forms ,但好在它的用法其實頗為簡單,主要的差異就只有在 Template Driven Forms 需要靠 viewProvider ,而 Reactive Froms 只要注入就行。

關於 viewProvider 與 provider 的差異,我推薦大家可以去看 Kevin (台灣 Angular GDE)的 [Angular] viewProviders V.S. providers ,我覺得寫得非常的清楚。

此外,如果覺得我分享不好,也可以參考 Kevin 的 [Angular] ControlContainer 的應用

今天的程式碼會放在 Github - Branch: day29 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!

如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!

評論

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×