今天來介紹 Angular 有個自定義表單元件,超級實用
在做一些資料傳遞的時候,能夠做一些處理以及統一管理表單樣式
首先要使用Angular 自定表單元件有兩個要實作
1. 實作ControlValueAccessor
interface ControlValueAccessor {
// 外部寫入值
writeValue(obj: any): void
// 外部傳入onChange function,透過這個function來跟表單控件通知值的異動
registerOnChange(fn: any): void
// 外部傳入onTouched function,透過這個function來跟表單控件通知touched狀態的異動
registerOnTouched(fn: any): void
// 外部傳入disabled的狀態
setDisabledState(isDisabled: boolean)?: void
}
2. 註冊提供者
@Component({
standalone: true,
selector: 'app-basic-input',
template: '',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BasicComponent),
multi: true,
}
]
})
export class BasicComponent
簡化提供者匯出
export const CONTROL_VALUE_ACCESSOR = (component: Type<any>) => {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => component),
multi: true,
};
};
然後像以下方式使用
@Component({
selector: 'app-select',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
providers: [CONTROL_VALUE_ACCESSOR(SelectComponent)],
})
export class SelectComponent
然後寫一個類似基底在用的元件直接繼承使用
protected val = '';
// 用來接收 setDisabledState 的狀態
protected disabled = false;
// 用來接收 registerOnChange 和 onTouched 傳入的方法
protected onChange?: (value: any) => {};
protected onTouched?: () => {};
ngControl: NgControl | undefined;
control: AbstractControl | null | undefined;
constructor(@Inject(Injector) public injector: Injector, private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.ngControl = this.injector.get(NgControl);
}
ngAfterViewInit(): void {
this.control = this.ngControl?.control;
this.cdr.detectChanges();
}
// 以下是 ControlValueAccessor 需實做的方法
writeValue(val: any): void {
this.val = val;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
protected change() {
if (this.onChange && this.onTouched) {
this.onChange(this.val);
this.onTouched();
}
}
其他自定的元件只要繼承
@Component({
selector: 'app-select',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
providers: [CONTROL_VALUE_ACCESSOR(SelectComponent)],
})
export class SelectComponent extends BasicComponent {
@Input() options: Options = [];
@Input() size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
}
然後當值有異動時,調用基底的change來跟表單做通知說參數有異動即可
<select
class="select select-bordered border-2"
[disabled]="disabled"
[(ngModel)]="val"
(ngModelChange)="change()">
<option
*ngFor="let option of options"
[disabled]="option.disabled ?? false"
[value]="option.value">
{{ option.text }}
</option>
</select>