iT邦幫忙

0

[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


尚未有邦友留言

立即登入留言