在前兩篇的介紹中我們了解到了什麼是Angular中的form,並且對Reactive form進行了實作,不過當我們在開發專案的時候,由於form的code過於龐大,會希望將重複使用到的部分建立成子組件(sub-component),在傳統方法下,如果需要與子組件溝通會需要使用@Input()
與@Output()
,但是Angular提供了另一種方便簡單的方法可以達到一樣與子組件溝通的效果,這個方法便是Control Value Accessor(CVA)
。
我們在子組件(sub-component)中需要建立NG_VALUE_ACCESSOR
設定,當Angular在執行程式的時會檢查此組件是否有設定NG_VAULE_ACCESSOR
,若有便會將此組件(component)視為一個表單控制項(formControl)。
// MyCVAComponent.ts
import { Component, forwardRef } from '@angular/core';
@Component({
selector: 'app-my-cva-component',
template: '',
styleUrls: ['./my-cva-component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR, // set NG_VALUE_ACCESSOR
useExisting: forwardRef(() => MyCVAComponent),
multi: true,
},
],
})
我們在sub-component中設定了NG_VALUE_ACCESSOR
,代表我們將這個Component設定為一個FormControl,而forwardRef(() => MyCVAComponent)
中的forward代表把快轉,因為我們必須確保MyCVAComponent要先被建立後才將它建立為FormControl,所以可以避免建立FormControl的時候找不到Component實體
。
而multi: true
代表著NG_VALUE_ACCESSOR
是可以被注入到多個不同的實體中,只要是設定了NG_VALUE_ACCESSOR
的實體都可以被正確拿到而不會被覆蓋。
當我們建立好這個Component的token後,我們正式開始建立這個Control Value Accessor Component,我們要將這個Component建立為Angular規定的ControlValueAccessor形式,首先我們需要引入它的interface,Angular提供的Interface規定了需要有以下幾種method:
// MyCVAComponent.ts
export Clsaa MyCVAComponent inplements ControlValueAccessor {
private disabled = false;
constructor() {}
writeValue(value: any) {};
registerOnChange(fn: (_: any) => void): void {};
registerOnTouched(fn: (_: any) => void): void {};
setDisabledState(isDisable: boolean) {
this.disabled = isDisable;
};
}
這樣我們就建立好一個CVA Component,接下來需要從父組件中綁定和給定form default value。
我們將CVA Component建立完成後,需要在父組件中綁定這個建立好的CVA並給予他初始值。
// app.component
import { Component } from '@angular/core';
import { FromControl, FormGroup, FormBuilder } from '@angular/forms'
@Component({
selector: 'app-root',
template: `
<form [formGroup]="myForm">
<app-my-cva-component formControlName="name"></app-my-cva-component>
</form>
<pre>{{myForm.value | json}}</pre>
`,
> styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'control-value-accessor';
myForm: FormGroup;
constructor(private fb: FormBuilder) {
this.myFrom = this.fb.group({
name: new FromControl({
value: 'Fandix Huang',
disabled: false
}) // get default value
})
}
}
在父組建中建立一個formGroup,必在其中new一個名為name
的FormControl
並給予Fandix Huang
的初始值,而我們在templete中引用子組件的tag,透過FormControlName
將我們的CVA Component綁定到父組件中的formControl,這樣就可以完成form綁定並給予CVA Component default value。
我們設定好父組件中的FormControl與將值綁定給CVA Component後,我們要對CVA Component進行一些設定。
export class MyCVAComponent implements ControlValueAccessor {
private formControl: FormControl;
private disabled: boolean;
constructor(private fb: FormBuilder) {
this.formControl = new FormControl('');
}
writeValue(val): void{
this.formControl.setValue(val); // set default value
}
registerOnChange(fn: (_: any) => void): void {
this.formControl.valueChanges.subscribe(fn);
}
registerOnTouched(fn: (_: any) => void): void {};
}
這樣我們就可以將父組件與CVA組建進行綁定。
對於開發一些專案中會需要對表單的更改權限進行設定,當權限不足時便將form表格disable以不讓其更改。
我們在父組件的templete中新增個button讓我們控制是否要disable這個formControl。
<!-- app.component.html -->
<button (click)="onDisable()">disable</button>
在父組件中新增一個onDisable
method已更改disable的狀態。
export class AppComponent {
title = 'control-value-accessor';
myForm: FormGroup;
private isDisable;
constructor(private fb: FormBuilder) {
this.myFrom = this.fb.group({
name: new FromControl({
value: 'Fandix Huang',
disabled: false
}) // get default value
})
}
// create method onDisable
onDisable(): void{
this.isDisable = !this.isDisable
if (isDisable) {
this.myForm.disable();
} else {
this.myForm.enable();
}
}
}
接下來我們在CVA Component中新增setDisabledState
,他會接收到從父組件傳遞下來的disable status。
export class MyCVAComponent implements ControlValueAccessor {
formControl: FormControl;
private disable: boolean;
constructor(private fb: FormBuilder) {
this.formControl = new FormControl('');
}
writeValue(val): void{
this.formControl.setValue(val);
}
registerOnChange(fn: (_: any) => void): void {
this.formControl.valueChanges.subscribe(fn);
}
registerOnTouched(fn: (_: any) => void): void {};
// create
setDisabledState(isDisable: boolean) {
this.disable = isDisable;
if (this.disable) {
this.formControl.disable();
} else {
this.formControl.enable();
}
}
}
參考文獻:
[Angular 大師之路] Day 08 - 自訂表單控制項
Angular Control-Value_Accessor