[Angular] Forms - Control Value Accessor [上]

前言

在前两篇的介绍中我们了解到了什麽是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.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的实体都可以被正确拿到而不会被覆盖。


建立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.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一个名为nameFormControl并给予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组建进行绑定。
img

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();
    }
  }
}

img


参考文献:
[Angular 大师之路] Day 08 - 自订表单控制项
Angular Control-Value_Accessor


<<:  Gulp 基础介绍 gulp-sass DAY80

>>:  Internxt Drive - 替代 Google Drive 免费 2 GB 云端空间服务,采用加密与分布式技术保护你的网路资料

Ascii - 产生 3D 旋转甜甜圈的甜甜圈形 C 程序码参考笔记

Ascii - 产生 3D 旋转甜甜圈的甜甜圈形 C 程序码参考笔记 参考资料 参考资料: Donu...

【Day27】Figma篇 : 设计到切版

对於设计师来说使用UI设计软件,除了可以善用之前提到的那些设计工具来增加效率和提升设计方法以外,还有...

Day21 - 用 Ruby on Rails 抓台湾证券交易所资料-除权除息计算结果表

前言 这篇主要以抓「台湾证券交易所」的「除权除息计算结果表」为主 取得「除权除息计算结果表」CSV ...

Day 14 : 笔记篇 01 — 了解 Obsidian 的 Metadata,建立一套可持续迭代的笔记系统

前言 Day 1 ~ Day 13 讲了 Obsidian 的基础操作、笔记理论後,接下来我要分享如...

CWE和CVE

在软件开发生命周期(SDLC)的设计阶段(建筑和详细设计)完成後,我们必须进行审查。威胁建模是设计审...