前言
在前两篇的介绍中我们了解到了什么是Angular中的form,并且对Reactive form进行了实作,不过当我们在开发专案的时候,由于form的code过于庞大,会希望将重複使用到的部分建立成子组件(sub-component),在传统方法下,如果需要与子组件沟通会需要使用@Input()
与@Output()
,但是Angular提供了另一种方便简单的方法可以达到一样与子组件沟通的效果,这个方法便是Control Value Accessor(CVA)
。
自订 NG_VALUE_ACCESSOR
我们在子组件(sub-component)中需要建立NG_VALUE_ACCESSOR
设定,当Angular在执行程式的时会检查此组件是否有设定NG_VAULE_ACCESSOR
,若有便会将此组件(component)视为一个表单控制项(formControl)。
// MyCVAComponent.tsimport { 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
的实体都可以被正确拿到而不会被覆盖。
建立Control Value Accessor
当我们建立好这个Component的token后,我们正式开始建立这个Control Value Accessor Component,我们要将这个Component建立为Angular规定的ControlValueAccessor形式,首先我们需要引入它的interface,Angular提供的Interface规定了需要有以下几种method:
writeValue(value: any): 当form data被元件外部元件(父组件)更改时调用。registerOnChange(fn: any): 当CVA Component中的form值发生更改时调用(传值给父组件)。registerOnTouched(fn: any): 当CVA Component中的form被点击时调用。setDisabledState(isDisable: boolean)?: 当disabled状态改变时调用。// MyCVAComponent.tsexport 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.componentimport { 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。
绑定CVA Component
我们设定好父组件中的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
对于开发一些专案中会需要对表单的更改权限进行设定,当权限不足时便将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