iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
Modern Web

簡單的事 最困難-Angular動態Form元件系列 第 20

第20天 重構組合元件 及 組合元件radio+text

  • 分享至 

  • xImage
  •  

今天我們要來寫另一個組合元件
我們有個需求是需要一組radiobutton的是或否
如果勾是 則後面的文字欄才可以輸入 不然則disabled

在調整元件之前 我們之前已經寫了不少function
有些應該可以重複使用
所以這邊我們決定來創個shareService來存放
首先我們把form-input的function搬進來 做一些調整

/**
     * 透過name 取得欄位設定值
     * @param fieldSetting 
     * @param name 
     * @returns FieldSetting
     */
    getSetting(fieldSettings: FieldSetting[], name: string, type: string = 'name'): FieldSetting
    {
        return fieldSettings.find((obj: any) => obj[type] == name) || { cname: '', name: '', inputType: '' }
    }

    /**
     * 透過表單從傳入設定職組取得符合表單的設定值
     * @param settings 設定植
     * @param fg 表單
     * @returns FieldSetting[]
     */
    getSettingsByForm(settings: FieldSetting[], fg: FormGroup): FieldSetting[]
    {
        return Object.keys(fg.value).map(v =>
        {
            return settings.find(setting => setting.name === v) as FieldSetting;
        })
    }

    getControl(group: FormGroup, columnName: string)
    {
        return group.get(columnName) as FormControl
    }
    typeof(value: any)
    {
        return typeof value;
    }

combo-phone有個getSettingByGroupType
其實跟getSettingByName有點像
我們這邊改成getSetting type是第三個參數
非必填 如果沒填就是用name

調整後的form-input.component.html會是這樣

<div>
  {{ pageSetting.title }}
  <div [formGroup]="pageSetting.form!" *ngIf="pageSetting.form" class="row">
    <div *ngFor="let fieldObj of getGroupList(innerForm)" class="col-6">
      <ng-container *ngIf="shareService.typeof(fieldObj) === 'string'">
        <app-field-template
          [fieldSetting]="
            shareService.getSetting(pageSetting.fieldSettings, fieldObj)
          "
          [control]="shareService.getControl(pageSetting.form, fieldObj)"
        >
        </app-field-template>
        <!-- {{ getControl(fieldObj).value }} -->
      </ng-container>

      <ng-container *ngIf="shareService.typeof(fieldObj) !== 'string'">
        <app-combo-phone
          [fieldSettings]="
            shareService.getSettingsByForm(pageSetting.fieldSettings, fieldObj)
          "
          [inputForm]="fieldObj"
        >
        </app-combo-phone>
      </ng-container>
    </div>
  </div>
</div>

調整完form-input以後 來調整combo-phone

//combo-phone.component.ts

    constructor(public shareService: ShareService) { }

    ngOnInit(): void
    {
    }

    getControl(group: FormGroup, groupType: string)
    {
        return this.shareService.getControl(group, this.getSettingByGroupType(groupType).name);
    }

    // 用name取得setting
    getSettingByGroupType(groupType: string)
    {
        return this.shareService.getSetting(this.fieldSettings, groupType, 'groupType');
    }

html不需要調整 用shareService簡化寫法

調整完了以後現在來增加新的元件
我們先增加設定檔

{
    name: 'radioTextRadio1',
    cname: '控制輸入欄位',
    inputType: 'radio',
    defaultValue: 'true',
    groupName: 'radioText1',
    groupType: 'radioText-radio',
    options: [
        { label: '是', value: 'true' },
        { label: '否', value: 'false' },
    ]
},
{
    name: 'radioTextText1',
    cname: '控制輸入欄位',
    inputType: 'text',
    groupName: 'radioText1',
    groupType: 'radioText-text',
    placeholder: '',
    defaultValue: '勾選後才可輸入',
    validator: [Validators.maxLength(3)]
},

理論上現在設定檔會產生innerForm進而產生畫面
所以這個設定檔會產生一個radioText1的formGroup
不過現在沒有特別判斷 我們只有一個combo-phone
這樣這個formGroup會丟到combo-phone裡面
然後會因為沒有對應的groupType所以造成頁面故障
所以這邊我們要先調整一下form-input
進而判斷我們要用哪一個combo
那我們要怎麼判斷呢
這邊我們會有一個getSettingsByForm的settings
透過查詢裡面的groupType可以得知這符合哪個屬性
為了方便處理 我們再多包一層元件app-combo-field-template

// form-input.component.html
        <app-combo-field-template
          [fieldSettings]="
            shareService.getSettingsByForm(pageSetting.fieldSettings, fieldObj)
          "
          [inputForm]="fieldObj"
        >
        </app-combo-field-template>
// combo-field-template.ts
    @Input() fieldSettings!: FieldSetting[];
    @Input() inputForm!: FormGroup;
    constructor() { }

    // 查詢是否為某個groupType
    findFieldSetting(value: string)
    {
        return !!this.fieldSettings.find(setting => { return setting.groupType?.startsWith(value) })
    }
// combo-field-template.html
    <div>
      <div *ngIf="findFieldSetting('phone')">
        <app-combo-phone [fieldSettings]="fieldSettings" [inputForm]="inputForm">
        </app-combo-phone>
      </div>
      <div *ngIf="findFieldSetting('radioText')">
        <app-combo-radio-text
          [fieldSettings]="fieldSettings"
          [inputForm]="inputForm"
        >
        </app-combo-radio-text>
      </div>
    </div>

由於我groupType的命名邏輯是以關鍵字為開頭
所以這邊使用startsWith
這邊要補充說明一點
groupName是用來組成組合元件用的
同一個組合元件的groupName會一樣 命名沒有特殊規則
groupType是組合元件後 對應元件內的對應位置用的
基本上會對應組合元件內的元件 要依循元件規則命名
上面也可以看到我們多加了一個combo-radio-text元件
傳入的數值跟cobmo-phone一樣

// combo-radio-text.component.html
<div>
  <div class="row">
    <span>
      {{ getSettingByGroupType("radioText-radio").cname }}
    </span>
    <div class="col-4">
      <app-field
        [fieldSetting]="getSettingByGroupType('radioText-radio')"
        [control]="getControl(inputForm, 'radioText-radio')"
      ></app-field>
    </div>

    <div class="col-8">
      <app-field
        [fieldSetting]="getSettingByGroupType('radioText-text')"
        [control]="getControl(inputForm, 'radioText-text')"
        [disabled]="getControl(inputForm, 'radioText-radio').value == 'false'"
      ></app-field>
    </div>
  </div>
</div>

ts的部分跟combo-phone一樣就不贅述
html的部分 因為這次的元件用到兩種不同的基本元件
這時候才想到 應該要用包裝好的app-field阿
另外這次會需要用到disabled的部分
所以在field.component裡面也要增加disabled這個input

    @Input() fieldSetting!: FieldSetting;
    @Input() control!: FormControl;
    @Input() disabled = false;

    constructor() { }

    ngOnChanges(): void
    {
        this.fieldSetting.disabled = this.disabled;
    }

這邊我們增加disabled這個input
但如果要一個一個往下傳的話每個base元件都要調整
好在我們base現在已經吃fieldSetting了
我們就在fieldSetting裡面增加一個disabled屬性
然後這邊要注意 原本的ngOnInit沒用到
這邊我們改成使用ngOnChanges
差異是ngOnInit只會在初始化的時候渲染一次
ngOnChanges是input變動的時候都會執行
因為我們的disabled會變動 所以需要做一個更新到fieldSetting的動作

// base-text.component.html
<div>
  <input
    type="text"
    placeholder="{{ fieldSetting.placeholder || '文字輸入框' }}"
    [formControl]="control"
    [attr.disabled]="fieldSetting.disabled ? '' : null"
    [ngClass]="{ 'is-invalid': control.invalid && control.touched }"
  />
  <span *ngIf="getError()" style="color: red">{{ getError() }}</span>
</div>

這邊我們用attr.disabled
然後給予空白與null
這是之前多方嘗試以後才知道
這邊要控制attr.disabled要失效要給null
給false沒有作用

原本想說加個元件這篇應該很短
結果牽涉到要重構就越改越多了
不過重構完以後要添加新的元件就方便多了
明天再來討論一個滿常見到又比較複雜的 地址元件

今日程式:day20


上一篇
第19天 組合元件-電話(下)
下一篇
第21天 地址元件
系列文
簡單的事 最困難-Angular動態Form元件30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言