寫angular的一定對FormControl不陌生
相對的Validator對於FormControl更是像影子一樣的存在
所以今天我們要來試著在我們的設定檔裡面加上Validator
首先來調整main的設定檔
// main.component.ts
// 以base-text為例
{
name: 'text1',
cname: '文字欄位1',
inputType: 'text',
placeholder: '請輸入文字',
required: true,
defaultValue: '123',
validator:[Validators.required,Validators.max(100)]
},
這邊我們在設定檔直接使用陣列紀錄要使用的原生Validators
陣列的話方便之後用formBuilder可以直接引用
然後設定檔一路傳到最裡面的base....
等等 忽然發現base-element裡面接收的是value
這是一開始沒有考慮到的 這邊如果要在元件裡面處理formControl上面的錯誤訊息
應該還是要把formControl傳到元件這層上面
於是乎 又要開始調整程式了
// base-element.component.ts
import { Directive, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, FormControl } from '@angular/forms';
import { FieldSetting } from './field-setting.model';
import { Subscription } from 'rxjs';
@Directive()
export abstract class BaseElementComponent implements ControlValueAccessor, OnInit, OnDestroy {
@Input() fieldSetting!: FieldSetting;
@Input() control!: FormControl; // 接收 FormControl
protected valueChangesSubscription: Subscription = new Subscription(); // 用來追蹤訂閱
onChange: any = () => { };
onTouch: any = () => { };
//這邊加上control的監聽
ngOnInit(): void {
// console.log('control',this.control)
// 監聽 FormControl 的值變化
if (this.control) {
this.valueChangesSubscription = this.control.valueChanges.subscribe((newValue: any) => {
this.onChange(newValue);
});
}
}
// subscribe後養成清空的習慣
ngOnDestroy(): void {
if (this.valueChangesSubscription) {
this.valueChangesSubscription.unsubscribe(); // 取消訂閱
}
}
writeValue(value: string | string[]): void {
if (this.control) {
this.control.setValue(value, { emitEvent: false });
}
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.control.disable();
} else {
this.control.enable();
}
}
// 因為監聽的關係 移除掉valueChange 有需要再各自寫
}
我們在base-element新增了input control 移除了原本的value
並且在init的時候幫control寫了一個valueChange
這樣原本的valueChange就可以移除
如果有需要其他的valueChange就各自在另外寫
然後既然control有subscribe
在生命週期destory的時候要記得unsubscribe
// 已text為例
// field.component.html
<app-base-text
[fieldSetting]="fieldSetting"
[control]="control"
></app-base-text>
// base-text.component.html
<div>
<input
type="text"
placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
[formControl]="control"
/>
</div>
這邊調整了傳遞的部分
然後移除了change的部分 因為現在讓FormControl處理
base-select沒甚麼問題 只要改html
// base-select.component.html
<div>
<select [formControl]="control">
<option *ngFor="let option of fieldSetting.options" [value]="option.value">
{{ option.label }}
</option>
</select>
</div>
base-radio跟base-checkbox有點特殊 要另外寫方法去綁定
// base-radio.component.html
<div>
<label *ngFor="let option of fieldSetting?.options">
<input
type="radio"
[name]="fieldSetting.name"
[value]="option.value"
[checked]="option.value == control.value"
(change)="onRadioChange(option.value)"
/>
<span><b></b>{{ option.label }}</span>
</label>
</div>
// base-radio.component.ts
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { BaseElementComponent } from '../base-element.component';
@Component({
selector: 'app-base-radio',
templateUrl: './base-radio.component.html',
styleUrls: ['./base-radio.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BaseRadioComponent),
multi: true
}
]
})
export class BaseRadioComponent extends BaseElementComponent
{
//另外寫事件去更新control onChange
onRadioChange(value: string): void {
this.control.setValue(value);
this.onChange(value); // 更新 ControlValueAccessor 的值
this.onTouch(); // 標記為已觸碰
}
}
radio用valueChange會有點問題 改用這個方法處理
// 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)="onCheckboxChange(option.value)"
/>
<span> <b></b>{{ option.label }}</span>
</label>
</div>
// base-checkbox.component.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 ngOnInit(): void {
// 不需要監聽了 但還是需要初始值轉換成陣列
if (this.control) {
this.checkedValue = typeof this.control.value == 'string' ? this.control.value.split(',') : this.control.value;
this.options = this.fieldSetting.options?.map(opt => { return { ...opt, checked: !!this.checkedValue.find(c => c === opt.value) } }) || []
}
}
// 沿用原本的valueChange
onCheckboxChange(value:string) {
let thisOption = this.options.find(opt => opt.value == value);
thisOption.checked = !thisOption.checked;
this.checkedValue = this.options.filter(opt => opt.checked).map(opt => opt.value);
let returnValue = this.checkedValue.join(',');
this.control.setValue(returnValue)
this.onChange(returnValue);
this.onTouch();
}
}
checkbox的話我們override onInit以及把原本的valueChange改成onCheckboxChange
這邊一不留神就寫了一大堆
還沒有正式進入檢核
那這篇就改成事前準備好了
明天再來正式處理傳進來的validators
今日程式:day12