昨天調整了base-text
今天來調整其他的
調整著調整著覺得
重複的程式碼太多了吧
有沒有辦法將重複的部分共用呢
這邊我們考慮使用extends
用一個基本的模板把大多數會用到的東西寫進去
於是乎我們新增一個base-element.component.ts
這個沒有要產生模板所以直接新增一個空白的程式
// base-element.component.ts
import { Directive, Input } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { FieldSetting } from './field-setting.model';
@Directive()
export abstract class BaseElementComponent implements ControlValueAccessor
{
@Input() fieldSetting!: FieldSetting;
@Input() value: string | string[] = '';
onChange: any = () => { };
onTouch: any = () => { };
writeValue(value: string | string[]): void
{
this.value = value;
}
registerOnChange(fn: any): void
{
this.onChange = fn;
}
registerOnTouched(fn: any): void
{
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean): void
{
// 可以根據需求實現禁用邏輯
}
valueChange(event: Event): void
{
const input = event.target as HTMLInputElement;
this.value = input.value;
this.onChange(input.value);
this.onTouch();
}
}
由於每個元件都會用到上述這些部分
所以我們把這些都提取出來
由於這個ts沒有模板 所以我們給他@Directive
另外改傳遞control以後 元件只需要知道自己的control不需要知道整體的fieldObj
所以input移除掉fieldObj
欄位變動的方法統一命名為valueChange
如果其他有一些需要調整的 再使用override 去覆寫
抽取模板以後其餘程式會這樣
// field-template.component.html
<div style="padding-bottom: 8px">
<span>
{{ fieldSetting.cname }}
<span *ngIf="fieldSetting.required" style="color: red">*</span>:
</span>
<!-- 傳入control 移除fieldObj -->
<app-field [fieldSetting]="fieldSetting" [control]="control"></app-field>
</div>
// field.component.ts
// 傳入control 移除fieldObj
@Input() fieldSetting!: FieldSetting;
@Input() control!: FormControl;
// field.component.html
<!-- 傳入control 移除fieldObj -->
<div [ngSwitch]="fieldSetting.inputType">
<div *ngSwitchCase="'text'">
<app-base-text
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-text>
</div>
<div *ngSwitchCase="'tel'">
<app-base-tel
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-tel>
</div>
<div *ngSwitchCase="'number'">
<app-base-number
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-number>
</div>
<div *ngSwitchCase="'password'">
<app-base-password
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-password>
</div>
<div *ngSwitchCase="'select'">
<app-base-select
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-select>
</div>
<div *ngSwitchCase="'radio'">
<app-base-radio
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-radio>
</div>
<div *ngSwitchCase="'checkbox'">
<app-base-checkbox
[fieldSetting]="fieldSetting"
[formControl]="control"
></app-base-checkbox>
</div>
</div>
而元件的部分一下子就變得很簡單
// base-text.component.html
<div>
<input
type="text"
placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
[value]="value"
(input)="valueChange($event)"
/>
</div>
// base-text.compoentn.ts
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { BaseElementComponent } from '../base-element.component';
@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 extends BaseElementComponent
{
}
以上以base-text為範例
其餘像是base-number,base-tel,base-password,base-radio內容都差不多
直接繼承BaseElementComponent就好
另外兩個有點不太一樣
// base-select.component.html
// 調整change的方法名
<div>
<select [value]="value" (change)="valueChange($event)">
<option *ngFor="let option of fieldSetting.options" [value]="option.value">
{{ option.label }}
</option>
</select>
</div>
// base-select.compoentn.ts
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { BaseElementComponent } from '../base-element.component';
@Component({
selector: 'app-base-select',
templateUrl: './base-select.component.html',
styleUrls: ['./base-select.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BaseSelectComponent),
multi: true
}
]
})
export class BaseSelectComponent extends BaseElementComponent
{
override valueChange(event: Event): void
{
const select = event.target as HTMLSelectElement;
this.value = select.value;
this.onChange(this.value);
this.onTouch();
}
}
這邊其實就是接的型別不一樣改成HTMLSelectElment而以
然而我們來看一下checkbox
因為我們有偷改傳進來的格式
是用字串加上逗號去分隔
所以我們要改傳進來的處理跟回傳的處理兩個地方
// base-checkbox.component.html
<div>
<label class="checkbox" *ngFor="let option of options">
<input
type="checkbox"
[checked]="option.checked"
[name]="fieldSetting.name"
value="{{ option.value }}"
(change)="valueChange($event)"
/>
<span> <b></b>{{ option.label }}</span>
</label>
</div>
// base-checkbox.compoentn.ts
import { Component, forwardRef } from '@angular/core';
import { BaseElementComponent } from '../base-element.component';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-base-checkbox',
templateUrl: './base-checkbox.component.html',
styleUrls: ['./base-checkbox.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BaseCheckboxComponent),
multi: true
}
]
})
export class BaseCheckboxComponent extends BaseElementComponent
{
checkedValue!: string[];
options!: any[];
override writeValue(value: string | string[]): void
{
this.value = value;
this.checkedValue = typeof this.value == 'string' ? this.value.split(',') : this.value;
this.options = this.fieldSetting.options?.map(opt => { return { ...opt, checked: !!this.checkedValue.find(c => c === opt.value) } }) || []
}
override valueChange(event: Event)
{
const input = event.target as HTMLInputElement;
let thisOption = this.options.find(opt => opt.value == input.value);
thisOption.checked = !thisOption.checked;
this.checkedValue = this.options.filter(opt => opt.checked).map(opt => opt.value);
this.value = this.checkedValue.join(',');
// 最後這邊改為用onChange
this.onChange(this.value);
this.onTouch();
}
}
終於改完今天的部分
寫到一半覺得方向要大改 又多花了一點時間
當初這部分也是折磨我好長一段間
調整了一下寫法變得精簡更多
明天應該就可以開始處理驗證的部分了
今日程式:day11