前言
專案中需要實現一個模組,任務管理,這裡的任務是多樣的。比如說打疫苗是一個任務,我們首先建立這個任務,然後在任務中規定幾個欄位,比如說打疫苗的地點,時間,接種疫苗品牌等等。關愛老人是另一個任務,任務中也規定幾個欄位,何時去關愛老人,行為記錄等。打疫苗時間和關愛老人時間需要用到時間選擇元件,打疫苗地點個接種疫苗品牌需要用到列表選擇元件,行為記錄需要用到文字輸入元件。不同的欄位需要用到不同的輸入元件。
E-R圖
一個任務對應多個表單項,一個表單項有一個表單型別。一個任務對應每一個居民生成一個任務詳情,一個表單項和一個任務詳情共同對應一個表單值。比如說打疫苗這是任務,打疫苗的時間,打疫苗的地點是表單項,打疫苗的時間(表單項)對應表單型別是時間(表單型別),打疫苗的地點(表單項)對應表單型別是單向選擇(表單型別)。打疫苗這個任務對應張三(居民)生成張三打疫苗任務(任務詳情),對應李四(居民)生成李四打疫苗任務(任務詳情),張三打疫苗任務(任務詳情)的打疫苗時間(表單項)是2022年3月18日(表單值),李四打疫苗任務(任務詳情)的打疫苗時間(表單項)是2022年3月10日(表單值)。
動態表單
對於設定一個任務的多個表單項很簡單,無非就是選擇一下表單項的表單型別,表單型別比如文字,時間,單選,多選等。問題是如何根據表單型別顯示不同的輸入方式。比如表單型別是時間就顯示一個時間選擇器,表單型別是文字就顯示一個輸入框。當然我們可以根據表單型別去進行判斷,不同的表單型別顯示不同元件,如下圖。
但是這意味著如果我們日後每新增一種表單型別,就要去增加一個if,也就是變動一次程式碼,這顯然不是我們想要的。
angular有動態表單的功能,幫助我們脫離繁雜的if判斷(在增加一個新的表單型別時,動態表單也需要改動程式碼,比如新增一個表單型別對應元件)。
首先我們需要定義一個指令,這個指令的作用是標記一個位置告訴angular把元件插到什麼地方。
@Directive({
selector: '[FormItem]',
})
export class FormDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
然後我們建立主頁面元件v層
<p>form-detial works!</p>
<div class="ad-banner-example">
<h3>任務詳情</h3>
<ng-template FormItem></ng-template>
</div>
我們將我們定義的名叫FormItem指令放入到ng-template標籤中,ng-template是一個空白容器,不會有任何的負作用。
主頁面c層
export class IndexComponent implements OnInit, OnDestroy {
formItems: FormItem[] = [];
@ViewChild(FormDirective, {static: true}) adHost!: FormDirective;
interval: number|undefined;
constructor(private formService: FormService) {}
ngOnInit() {
this.formItems = this.formService.getFormData();
this.loadComponent();
}
ngOnDestroy() {
clearInterval(this.interval);
}
loadComponent() {
for (var i = 0; i < this.formItems.length; i++) {
const adItem = this.formItems[i];
const viewContainerRef = this.adHost.viewContainerRef;
const componentRef = viewContainerRef.createComponent<FormComponent>(adItem.component);
componentRef.instance.data = adItem.data;
}
}
}
初始化時,先通過服務層獲取表單項陣列,然後迴圈遍歷每一個表單項,最後通過viewContainerRef的createComponent()方法載入表單項型別對應元件。
對於不同的表單型別輸入元件,我們先規定一個公共父類介面。
export interface FormComponent {
data: any;
}
所有的表單型別輸入元件繼承這個介面,比如說單選表單型別
@Component({
selector: 'app-select',
templateUrl: '
<p>{{data.text}}</p>
<select class="custom-select" id="inputGroupSelect02">
<option *ngFor="let selection of data.selections" [value]="selection.value">{{selection.name}}</option>
</select>
',
styleUrls: ['./select.component.css']
})
export class SelectComponent implements OnInit, FormComponent {
constructor() { }
ngOnInit(): void {
}
@Input() data: any;
}
文字表單型別
@Component({
selector: 'app-text',
templateUrl: '
<p>{{data.text}}</p>
<input type="text">
',
styleUrls: ['./text.component.css']
})
export class TextComponent implements OnInit, FormComponent {
constructor() { }
ngOnInit(): void {
}
@Input() data: any;
}
繼承關係
最終效果