元件類型都差不多以後
要進入另一個重點
我們用設定檔產生表單
還會需要幾個功能
在這個情況下我們需要能處理檢核的元件
這樣似乎需要調整一下我們的架構
使用field-template的地方(目前是main)
需要知道這個欄位檢核有沒有通過
而且field-template目前是單一欄位
所以在main應該會需要有一個formGroup去包含全部的formControl
再用這個formGroup的invalid去判斷表單是不是通過驗證
這樣我們現在要做的調整有以下
今天的話應該現做元件的改寫
我們會先改main產生fromGroup
然後會調整field-template改傳遞
// main.component.ts
fieldForm!: FormGroup; // 增加formGroup
constructor(private fb: FormBuilder) { } // 使用formBuilder方便obj轉formGroup
ngOnInit(): void {
// 用reduce將name作為key值 defaultValue作為Value
this.fieldObj = this.fieldSettings.reduce((acc: any, item) => {
acc[item.name] = item.defaultValue || '';
return acc;
}, {});
this.fieldForm = this.fb.group(this.fieldObj); // 增加此行將obj轉成formGroup
}
// html需要使用到
getControl(columnName:string){
return this.fieldForm.get(columnName) as FormControl
}
// main.component.html
<div [formGroup]="fieldForm">
<div *ngFor="let fieldSetting of fieldSettings">
<app-field-template [fieldSetting]="fieldSetting" [fieldObj]="fieldObj"
[control]="getControl(fieldSetting.name)">
</app-field-template>
<!-- {{ getControl(fieldSetting.name).value }} -->
</div>
</div>
從上面可以看到 我們增加了一個fieldForm來處理整個表單
直接透過fieldBuild將原本的fieldObj轉成fieldForm
這邊因為隔了太多層 所以決定直接把對應的formControl傳遞進去
傳遞到最裡面再處理
由於要傳對應的FormControl
所以這邊有多寫了一個getControl的方法去取得對應FormControl
用來測試的註解部分也稍微調整一下
field-template跟field的部分直接左手進右手出
// field-template.component.ts
@Input() control!:FormControl;
// field-template.component.html
// 增加傳遞control
<app-field [fieldSetting]="fieldSetting" [fieldObj]="fieldObj" [control]="control"></app-field>
// field.component.ts
@Input() control!:FormControl;
// field.component.html
// 以base-text為例
<app-base-text
[fieldSetting]="fieldSetting"
[fieldObj]="fieldObj"
[formControl]="control"
></app-base-text>
然後關鍵就是在base元件的改動
現在base元件想要直接承接formContol來處理
所以需要implements ControlValueAccessor
首先要確保元件註冊NG_VALUE_ACCESSOR
@Component({
selector: 'app-base-text',
templateUrl: './base-text.component.html',
styleUrls: ['./base-text.component.scss'],
providers:[
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BaseTextComponent),
multi: true
},]
})
然後implements ControlValueAccessor以後會有下面這些要處理
writeValue(obj: any): void {
throw new Error('Method not implemented.');
}
registerOnChange(fn: any): void {
throw new Error('Method not implemented.');
}
registerOnTouched(fn: any): void {
throw new Error('Method not implemented.');
}
setDisabledState?(isDisabled: boolean): void {
throw new Error('Method not implemented.');
}
writeValue是處理寫入數值
registerOnChange及registerOnTouched就是處理change跟touch的事件
第四個我們還沒用到
調整以後的base-text大概會是這樣
// base-text.component.html
<div>
<input
type="text"
placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
[value]="value"
(input)="onInput($event)"
/>
</div>
// base-text.component.ts
import { Component, forwardRef, Input } from '@angular/core';
import { FieldSetting } from '../field-setting.model';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-base-text',
templateUrl: './base-text.component.html',
styleUrls: ['./base-text.component.scss'],
providers:[
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BaseTextComponent),
multi: true
},]
})
export class BaseTextComponent implements ControlValueAccessor {
@Input() fieldSetting!: FieldSetting;
@Input() fieldObj!: any;
@Input() value: string = '';
// 這些是 ControlValueAccessor 的實現
onChange: any = () => { };
onTouch: any = () => { };
writeValue(value: any): void {
this.value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error('Method not implemented.');
}
onInput(event: Event): void {
const input = event.target as HTMLInputElement;
this.onChange(input.value);
this.onTouch();
}
}
原本的valueChange調整為onInput並且綁訂在input事件上
這邊因為main.component以及base元件的部分都有使用到ReactiveFormsModule
記得在app.module.ts以及element.modul.ts內引入
然後到main把註解拿掉測試看看有沒有問題
接著嘗試也用同樣方法改動其他base的時候
發現有其他問題
那我們留著明天一併處理好了 今天就先調整base-text
今日程式:day10