昨天跟大家分享了自訂表單元件的作法,但昨天的作法只適用於一個欄位、一個 FormControl
。
雖然 FormControl
裡是可以設 {}
的值,但如果我們真的想要的是一個可以直接用 [formGroup]
、 [formArray]
所使用的元件呢?
沒問題,只要你想要, Angular 都給你
大家還記得之前我們做了個「被保人表單」吧?
一開始只有「姓名」、「性別」跟「年齡」這三個欄位,後來我們加了「聯絡資訊」的欄位,這次我們再幫它加個「聯絡地址」的欄位吧!
一般來說,聯絡地址的欄位通常會分成「縣市」、「鄉鎮市區」、「郵遞區號」與「地址」,而「縣市」、「鄉鎮市區」與「郵遞區號」之間會有一些連動邏輯,縣市」與「鄉鎮市區」這兩個欄位也通常會是下拉選單,其他的則是一般的 input
欄位。
首先一樣先把 HTML 準備好,像這樣:
<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>
畫面:
樣式用
inline
的方式設定是方便教學,小朋友們要盡量少用噢!
接著,在 .ts
裡加入地址的相關欄位的 FormGroup
與 FormControl
:
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
});
然後再將其綁與畫面上元素綁定,像這樣:
<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>
連動邏輯的實作就交給大家練習囉,我們今天沒有要著重於此部分的處理。
至此,我們就完成了第一步的準備工作。
接下來,我們就要將聯絡地址這塊拆成一個獨立的 Component ─ AddressInfoComponent
。
首先,先將 HTML 搬過去並稍微調整一下:
<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
:
export class AddressInfoComponent {
constructor(private controlContainer: ControlContainer) { }
}
然後加上:
get formGroup(): FormGroup {
return this.controlContainer.control as FormGroup;
}
再回到被保人表單裡,把原本的聯絡地址區塊改成:
<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 的綁定方式,像是這樣:
<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
,而是改在 AddressInfoComponent
的 MetaData 的 viewProviders
裡新增以下設定:
@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 Forms 與 Reactive Forms ,但好在它的用法其實頗為簡單,主要的差異就只有在 Template Driven Forms 需要靠 viewProvider
,而 Reactive Froms 只要注入就行。
關於 viewProvider 與 provider 的差異,我推薦大家可以去看 Kevin (台灣 Angular GDE)的 [Angular] viewProviders V.S. providers ,我覺得寫得非常的清楚。
此外,如果覺得我分享不好,也可以參考 Kevin 的 [Angular] ControlContainer 的應用
今天的程式碼會放在 Github - Branch: day29 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!
如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!
Hi Leo,
不知道能否再說明一下ControlValueAccessor和ControlContainer使用的場景?
目前看完兩者,感覺同個GroupControl可以透過ControlContainer的方式讓表格樣貌變的多樣化但其驗證規則都一樣;ControlValueAccessor則是將一個GroupControl嵌入到一個FormControl裡面,使這個GroupControl的表格可以套用在多個想要有相同表格的表單裡,不知道這樣的想法對不對~
另外是否可以將ControlValueAccessor和ControlContainer結合在一起,用ControlValueAccessor將表格嵌入,再透過ControlContainer將表格的html樣式做改變,不知道這樣在實作上會不會更好?
不知道能否再說明一下ControlValueAccessor和ControlContainer使用的場景?
目前看完兩者,感覺同個GroupControl可以透過ControlContainer的方式讓表格樣貌變的多樣化但其驗證規則都一樣;ControlValueAccessor則是將一個GroupControl嵌入到一個FormControl裡面,使這個GroupControl的表格可以套用在多個想要有相同表格的表單裡,不知道這樣的想法對不對~
其實沒那麼複雜,簡單來說:
ControlValueAccessor
就是讓你把你自己做的 Component 變成可以讓別人使用 ngModel
或是 formControl
的方式來使用你做的 Component;而 ControlContainer
則是可以讓別人使用 ngModelGroup
或者是 formGroup
的方式來使用。
再更簡單說就是客製化表單元件啦!XDDD
另外是否可以將ControlValueAccessor和ControlContainer結合在一起,用ControlValueAccessor將表格嵌入,再透過ControlContainer將表格的html樣式做改變,不知道這樣在實作上會不會更好?
承上所述,你不覺得這兩個東西攪在一起是一件滿奇怪的事情嗎?XDDD