angular動態表單

小強Zzz發表於2022-03-23

前言

專案中需要實現一個模組,任務管理,這裡的任務是多樣的。比如說打疫苗是一個任務,我們首先建立這個任務,然後在任務中規定幾個欄位,比如說打疫苗的地點,時間,接種疫苗品牌等等。關愛老人是另一個任務,任務中也規定幾個欄位,何時去關愛老人,行為記錄等。打疫苗時間和關愛老人時間需要用到時間選擇元件,打疫苗地點個接種疫苗品牌需要用到列表選擇元件,行為記錄需要用到文字輸入元件。不同的欄位需要用到不同的輸入元件。

E-R圖

image.png
一個任務對應多個表單項,一個表單項有一個表單型別。一個任務對應每一個居民生成一個任務詳情,一個表單項和一個任務詳情共同對應一個表單值。比如說打疫苗這是任務,打疫苗的時間,打疫苗的地點是表單項,打疫苗的時間(表單項)對應表單型別是時間(表單型別),打疫苗的地點(表單項)對應表單型別是單向選擇(表單型別)。打疫苗這個任務對應張三(居民)生成張三打疫苗任務(任務詳情),對應李四(居民)生成李四打疫苗任務(任務詳情),張三打疫苗任務(任務詳情)的打疫苗時間(表單項)是2022年3月18日(表單值),李四打疫苗任務(任務詳情)的打疫苗時間(表單項)是2022年3月10日(表單值)。

動態表單

對於設定一個任務的多個表單項很簡單,無非就是選擇一下表單項的表單型別,表單型別比如文字,時間,單選,多選等。問題是如何根據表單型別顯示不同的輸入方式。比如表單型別是時間就顯示一個時間選擇器,表單型別是文字就顯示一個輸入框。當然我們可以根據表單型別去進行判斷,不同的表單型別顯示不同元件,如下圖。
image.png
但是這意味著如果我們日後每新增一種表單型別,就要去增加一個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;
}

繼承關係
image.png
最終效果
image.png

相關文章