iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0

寫angular的一定對FormControl不陌生
相對的Validator對於FormControl更是像影子一樣的存在
所以今天我們要來試著在我們的設定檔裡面加上Validator
首先來調整main的設定檔

// main.component.ts
// 以base-text為例
    {
        name: 'text1',
        cname: '文字欄位1',
        inputType: 'text',
        placeholder: '請輸入文字',
        required: true,
        defaultValue: '123',
        validator:[Validators.required,Validators.max(100)]
    },

這邊我們在設定檔直接使用陣列紀錄要使用的原生Validators
陣列的話方便之後用formBuilder可以直接引用
然後設定檔一路傳到最裡面的base....
等等 忽然發現base-element裡面接收的是value
這是一開始沒有考慮到的 這邊如果要在元件裡面處理formControl上面的錯誤訊息
應該還是要把formControl傳到元件這層上面
於是乎 又要開始調整程式了

// base-element.component.ts
import { Directive, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, FormControl } from '@angular/forms';
import { FieldSetting } from './field-setting.model';
import { Subscription } from 'rxjs';

@Directive()
export abstract class BaseElementComponent implements ControlValueAccessor, OnInit, OnDestroy {
    @Input() fieldSetting!: FieldSetting;
    @Input() control!: FormControl; // 接收 FormControl
    protected valueChangesSubscription: Subscription = new Subscription();  // 用來追蹤訂閱


    onChange: any = () => { };
    onTouch: any = () => { };

    //這邊加上control的監聽
    ngOnInit(): void {
        // console.log('control',this.control)
        // 監聽 FormControl 的值變化
        if (this.control) {
            this.valueChangesSubscription = this.control.valueChanges.subscribe((newValue: any) => {
                this.onChange(newValue);
            });
        }
    }
    
    // subscribe後養成清空的習慣
    
    ngOnDestroy(): void {
        if (this.valueChangesSubscription) {
            this.valueChangesSubscription.unsubscribe();  // 取消訂閱
        }
    }

    writeValue(value: string | string[]): void {
        if (this.control) {
            this.control.setValue(value, { emitEvent: false });
        }
    }

    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    registerOnTouched(fn: any): void {
        this.onTouch = fn;
    }

    setDisabledState(isDisabled: boolean): void {
        if (isDisabled) {
            this.control.disable();
        } else {
            this.control.enable();
        }
    }
    
    // 因為監聽的關係 移除掉valueChange 有需要再各自寫
}

我們在base-element新增了input control 移除了原本的value
並且在init的時候幫control寫了一個valueChange
這樣原本的valueChange就可以移除
如果有需要其他的valueChange就各自在另外寫
然後既然control有subscribe
在生命週期destory的時候要記得unsubscribe

// 已text為例
// field.component.html

    <app-base-text
      [fieldSetting]="fieldSetting"
      [control]="control"
    ></app-base-text>

// base-text.component.html
<div>
  <input
    type="text"
    placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
    [formControl]="control"
  />
</div>

這邊調整了傳遞的部分
然後移除了change的部分 因為現在讓FormControl處理

base-select沒甚麼問題 只要改html

// base-select.component.html
<div>
  <select [formControl]="control">
    <option *ngFor="let option of fieldSetting.options" [value]="option.value">
      {{ option.label }}
    </option>
  </select>
</div>

base-radio跟base-checkbox有點特殊 要另外寫方法去綁定

// base-radio.component.html
<div>
  <label *ngFor="let option of fieldSetting?.options">
    <input
      type="radio"
      [name]="fieldSetting.name"
      [value]="option.value"
      [checked]="option.value == control.value"
      (change)="onRadioChange(option.value)"
    />
    <span><b></b>{{ option.label }}</span>
  </label>
</div>
// base-radio.component.ts
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { BaseElementComponent } from '../base-element.component';

@Component({
    selector: 'app-base-radio',
    templateUrl: './base-radio.component.html',
    styleUrls: ['./base-radio.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => BaseRadioComponent),
            multi: true
        }
    ]
})
export class BaseRadioComponent extends BaseElementComponent
{
    //另外寫事件去更新control onChange
    onRadioChange(value: string): void {
        this.control.setValue(value);
        this.onChange(value); // 更新 ControlValueAccessor 的值
        this.onTouch();       // 標記為已觸碰
      }
}

radio用valueChange會有點問題 改用這個方法處理

// base-checkbox.component.html
<div>
  <label class="checkbox" *ngFor="let option of options">
    <input
      type="checkbox"
      [checked]="option.checked"
      [name]="fieldSetting.name"
      value="{{ option.value }}"
      (change)="onCheckboxChange(option.value)"
    />
    <span> <b></b>{{ option.label }}</span>
  </label>
</div>
// base-checkbox.component.ts
import { Component, forwardRef } from '@angular/core';
import { BaseElementComponent } from '../base-element.component';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
    selector: 'app-base-checkbox',
    templateUrl: './base-checkbox.component.html',
    styleUrls: ['./base-checkbox.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => BaseCheckboxComponent),
            multi: true
        }
    ]
})
export class BaseCheckboxComponent extends BaseElementComponent {
    checkedValue!: string[];
    options!: any[];

    override ngOnInit(): void {
        // 不需要監聽了 但還是需要初始值轉換成陣列
        if (this.control) {
            this.checkedValue = typeof this.control.value == 'string' ? this.control.value.split(',') : this.control.value;
            this.options = this.fieldSetting.options?.map(opt => { return { ...opt, checked: !!this.checkedValue.find(c => c === opt.value) } }) || []
        }
    }
    
    // 沿用原本的valueChange
    onCheckboxChange(value:string) {
        let thisOption = this.options.find(opt => opt.value == value);
        thisOption.checked = !thisOption.checked;

        this.checkedValue = this.options.filter(opt => opt.checked).map(opt => opt.value);
        let returnValue = this.checkedValue.join(',');

        this.control.setValue(returnValue)
        this.onChange(returnValue);
        this.onTouch();
    }
}

checkbox的話我們override onInit以及把原本的valueChange改成onCheckboxChange

這邊一不留神就寫了一大堆
還沒有正式進入檢核
那這篇就改成事前準備好了
明天再來正式處理傳進來的validators

今日程式:day12


上一篇
第11天 要改的東西太多了 不如就繼承吧
下一篇
第13天 validator檢核元件(下)
系列文
簡單的事 最困難-Angular動態Form元件30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言