formly-form 動態表單

2022-12-19 21:02:59

動態表單庫

https://github.com/ngx-formly/ngx-formly

安裝

ng add @ngx-formly/schematics --ui-theme=ng-zorro-antd
@ngx-formly/ng-zorro-antd

選擇UI

      • bootstrap
      • material
      • ng-zorro-antd
      • ionic
      • primeng
      • kendo
      • nativescript

會預設匯入到Module

+ import { ReactiveFormsModule } from '@angular/forms';
+ import { FormlyModule } from '@ngx-formly/core';
+ import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';

@NgModule({
  imports: [
    BrowserModule,
+   FormsModule,  
+   ReactiveFormsModule,
      
   // -----------
    FormlyNgZorroAntdModule,
    NzFormModule,
   //    -------  
      
+   FormlyModule.forRoot(),
+   FormlyBootstrapModule
  ],
  ...
})

formly-form

  <formly-form [form]="form" [fields]="fields" [model]="model"></formly-form>

<formly-form>元件是表單的主要容器

  • fields:用於構建表單的欄位設定。
  • form:允許跟蹤模型值和驗證狀態的表單範例。
  • model:表格要表示的模型
  form = new FormGroup({});
  model = { email: '[email protected]' };
  fields: FormlyFieldConfig[] = [
    {
      key: 'email',
      type: 'input',
      props: {
        label: 'Email address',
        placeholder: 'Enter email',
        required: true,
      }
    }
  ];
Name Type Default Required Description
form FormGroup or FormArray new FormGroup({}) no 表單範例
fields FormlyFieldConfig[] yes 構建表單的欄位設定
model any yes 表單表示的模型
options FormlyFormOptions no 表格的選項

(modelChange) model 值發生變數的時候觸發的事件

fields

Attribute Type Description
key string model 的鍵
id string 請注意,id如果未設定,則會生成。
name string 過於的表單name才有效
type string 自定義模板
className string 自定的class樣式formly-field directive.
props object 任何特定於模板的選項都放在此處
templateOptions object 任何特定於模板的選項都放在此處,props進行改用(props權重高一些)
template string 自定義html內容, 而不是type
defaultValue any model 為設定,或者為undefined, 該模型的值被分配
hide boolean 是否隱藏欄位
hideExpression boolean/string/function 有條件地隱藏該欄位
expressions boolean/string/function 一個物件,其中鍵是要在主欄位設定上設定的屬性,值是用於分配該屬性的表示式。
focus boolean 是否獲取焦點. 預設為 false. 可以用 expressions進行設定
wrappers string[] 自定義元件裡面包裝label , 可以設定樣式
parsers function[] 每當模型更新(通常通過使用者輸入)時,作為管道執行的函數陣列
fieldGroup FormlyFieldConfig[] 欄位組, 讓高階佈局更簡單, 對於通過模型關聯的欄位進行分組
fieldArray FormlyFieldConfig
fieldGroupClassName string formly-group元件的class
validation object 校驗messages 資訊的顯示
validators any 特定欄位設定驗證規則
asyncValidators any 非同步驗證的內容
formControl AbstractControl 該欄位的FormControl。它為您提供更多控制,如執行驗證器、計算狀態和重置狀態。
modelOptions object 控制模型更改的有用屬性的物件: debounce, updateOn
 modelOptions?: {
    debounce?: { // 防抖的ms 值
      default: number;
    };
    //  https://angular.io/api/forms/AbstractControl#updateOn 觸發方式
    updateOn?: 'change' | 'blur' | 'submit';
  };

formState 配合option 進行狀態通訊

NgModule 宣告中宣告驗證函數和訊息

自定義校驗

// 自定義報錯資訊
export function IpValidatorMessage(error: any, field: FormlyFieldConfig) {
  return `"${field.formControl.value}" is not a valid IP Address`;
}
// 報錯規則
export function IpValidator(control: AbstractControl): ValidationErrors | null {
  return /(\d{1,3}\.){3}\d{1,3}/.test(control.value) ? null : { 'ipTwo': true };
}
...
@NgModule({
  imports: [
    ...
    FormlyModule.forRoot({
      validationMessages: [
        { name: 'ipTwo', message: IpValidatorMessage },
        { name: 'required', message: 'This field is required' },
      ],
       validators: [
        { name: 'ipTwo', validation: IpValidator },
      ],
    }),
  ]
})

頁面上使用

{
  key: 'ip',
  type: 'input',
  props: {
    label: 'IP Address (using custom validation declared in ngModule)',
    required: true,
  },
  validators: {
    validation: ['ipTwo'],
  },
  // 非同步校驗器
  asyncValidators: {
    validation: ['ipAsync'],
  },    
},

欄位宣告校驗函數

export function IpValidator(control: AbstractControl): ValidationErrors {
  return /(\d{1,3}\.){3}\d{1,3}/.test(control.value) ? null : { 'ipTwo': true };
}

{
  key: 'ip',
  type: 'input',
  props: {
    label: 'IP Address (using custom validation through `validators.validation` property)',
    required: true,
  },
  validators: {
    validation: [IpValidator],
  },
  // 非同步函數
   asyncValidators: {
    validation: [IpAsyncValidator],
  },     
},

在欄位定義中宣告驗證函數和訊息

{
  key: 'ip',
  type: 'input',
  props: {
    label: 'IP Address (using custom validation through `validators.expression` property)',
    description: 'custom validation message through `validators.expression` property',
    required: true,
  },
  validators: {
    ip: {
        // 為true 為符合報錯資訊
      expression: (c: AbstractControl) => /(\d{1,3}\.){3}\d{1,3}/.test(c.value),
      message: (error: any, field: FormlyFieldConfig) => `"${field.formControl.value}" is not a valid IP Address`,
    },
  },
  asyncValidators: {
    ip: {
      expression: (c: AbstractControl) => return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(/(\d{1,3}\.){3}\d{1,3}/.test(c.value));
        }, 1000);
      }),
      message: (error: any, field: FormlyFieldConfig) => `"${field.formControl.value}" is not a valid IP Address`,
    },
  },
},

在 NgModule 宣告中的表單型別和訊息中宣告驗證函數

export function IpValidator(control: AbstractControl): boolean {
  return /(\d{1,3}\.){3}\d{1,3}/.test(control.value);
}

@NgModule({
  imports: [
    ...
    FormlyModule.forRoot({
      validationMessages: [
        { name: 'ipTwo', message: 'This field is required' },
      ],
      types: [
        {
          name: 'ipTwo',
          extends: 'input',
          defaultOptions: {
            validators: {
              ip: IpValidator // 'ip' matches the ip validation message
            }
          },
        },
    }),
  ]
})

形式表示式expression

{
  key: 'text2',
  type: 'input',
  props: {
    label: 'Hey!',
    placeholder: 'This one is disabled if there is no text in the other input',
  },
  expressions: {
    'props.disabled': '!model.text',
    'props.disabled': (field: FormlyFieldConfig) => {
      return !field.model.text;
    },
  },
},

條件

{
  key: 'iLikeTwix',
  type: 'checkbox',
  props: {
    label: 'I like twix',
  },
  expressions: { 
      hide: '!model.name',
      hide: (field: FormlyFieldConfig) => {
      return field.model?.city === "123";
    },
  },
}

點選

toggle(){
  this.fields[0].hide = !this.fields[0].hide;
}

formState 狀態

 <formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>
  form = new FormGroup({});
  model: any = {};
  options: FormlyFormOptions = {
    formState: {
      disabled: true,
    },
  };

  fields: FormlyFieldConfig[] = [
    {
      key: 'text',
      type: 'input',
      props: {
        label: 'First Name',
      },
      expressions: {
        // apply expressionProperty for disabled based on formState
        'props.disabled': 'formState.disabled',
      },
    },
  ];

  toggleDisabled() {
    this.options.formState.disabled = !this.options.formState.disabled;
  }

生命週期

   hooks: {
        afterContentInit: () => {},
        afterViewInit: () => {},
        onInit: () => {},
        onChanges: () => {},
        onDestroy: () => {},
      },

引數

export type FormlyHookFn = (field: FormlyFieldConfig) => void;

export interface FormlyHookConfig {
  onInit?: FormlyHookFn;
  onChanges?: FormlyHookFn;
  afterContentInit?: FormlyHookFn;
  afterViewInit?: FormlyHookFn;
  onDestroy?: FormlyHookFn;
}

例如

hooks:{
           onInit(f: FormlyFieldConfigCache) {
                    f.formControl = new FormControl();
                }
            },
}

fieldChanges

值得監聽(原始碼是對valueChanges進行封裝)

            hooks: {
                onInit(f: FormlyFieldConfigCache) {
                    f?.options?.fieldChanges?.subscribe(res => {
                        console.log(res,'ressss');
                    })
                }
            },
            modelOptions: {
                debounce: {default: 3000},// 防抖
                updateOn: 'change'
            }

自定義元件

import { Component } from '@angular/core';
import { FieldType, FieldTypeConfig } from '@ngx-formly/core';

@Component({
  selector: 'formly-field-input',
  template: `
    <input type="input" [formControl]="formControl" [formlyAttributes]="field">
  `,
})
export class InputFieldType extends FieldType<FieldTypeConfig> {}

ngModule 註冊元件

@NgModule({
  declarations: [InputFieldType],
    import { InputFieldType } from './intput-field.type';

@NgModule({
  imports: [
    FormlyModule.forRoot({
      types: [
        { name: 'inputOne', component: InputFieldType },
      ],
    }),
  ],
})

types 有兩個屬性

name:元件型別的名稱。type您在欄位的選項中使用它。
component:設定此型別時 Formly 應建立的元件。

頁面使用

方式一, 直接把元件傳遞給欄位使用

  fields: FormlyFieldConfig[] = [
    {
      key: 'firstname',
      type: InputFieldType,
    },
  ];

方式二, 使用ngModule 註冊的type

  fields: FormlyFieldConfig[] = [
    {
      key: 'firstname',
      type: 'inputOne',
    },
  ];

自定義label 元件

建立一個代表擴充套件FieldWrapper類的包裝器的元件。

import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';

@Component({
  selector: 'formly-wrapper-panel',
  template: `
    <div class="card">
      <h3 class="card-header">Its time to party</h3>
      <h3 class="card-header">{{ props.label }}</h3>
      <div class="card-body">
        <ng-container #fieldComponent></ng-container>
      </div>
      <!-- 報錯的資訊 -->
        <ng-container *ngIf="showError">
          <formly-validation-message [field]="field"></formly-validation-message>
        </ng-container>
    </div>
  `,
})
export class PanelFieldWrapper extends FieldWrapper {
}

使用

@NgModule({
  declarations: [PanelFieldWrapper],
  imports: [
    FormlyModule.forRoot({
      wrappers: [
        { name: 'panel', component: PanelFieldWrapper },
      ],
    }),
  ],
})

頁面使用

方式一 直接傳遞元件

fields: FormlyFieldConfig[] = [
  {
    key: 'address',
    wrappers: [PanelFieldWrapper],
    props: { label: 'Address' },
    fieldGroup: [{
      key: 'town',
      type: 'input',
      props: {
        required: true,
        type: 'text',
        label: 'Town',
      },
    }],
  },
];

方法二:將PanelFieldWrapper別名(在 中定義FormlyModule.forRoot)傳遞給欄位設定。

fields: FormlyFieldConfig[] = [
  {
    key: 'address',
+    wrappers: ['panel'],
    props: { label: 'Address' },
    fieldGroup: [{
      key: 'town',
      type: 'input',
      props: {
        required: true,
        type: 'text',
        label: 'Town',
      },
    }],
  },
];

在module組合使用

@NgModule({
  imports: [
    FormlyModule.forRoot({
      types: [
        {
          name: 'operator',
          component: OperatorComponent,   //輸入框元件
          wrappers: ['form-field']       //label 元件
        },
      ],
    }),
  ],

自定義擴充套件介面屬性

建立了一個擴充套件,它定義了一個預設標籤(如果它FormlyFieldConfig本身沒有定義的話)

定義介面類

*default-label-extension.ts*

import { FormlyExtension } from '@ngx-formly/core';

export const defaultLabelExtension: FormlyExtension = {
  prePopulate(field): void {
    if (field.props?.label) {
      return;
    }
    field.props = {
      ...field.props,
      label: 'Default Label'
    }
  },
};

FormlyExtension允許您定義最多三個方法,這些方法將在表單構建過程中按此順序呼叫:

  1. prePopulate
  2. onPopulate
  3. postPopulate

註冊自定義擴充套件

@NgModule({
  imports: [
    FormlyModule.forRoot({
      extensions: [
        {
          name: 'default-label',
          extension: defaultLabelExtension,
          priority:1 // 優先順序, 預設1
        }
      ]
    })
  ],
})

lazyRender 延遲載入元件

預設為true

當設定為false, 渲染欄位元件並使用CSS控制其可見性。

例如


  constructor(
        private config: FormlyConfig,
    ) {
              this.config.extras.lazyRender = false;
    }
    fields: FormlyFieldConfig[] = [
        {
            key: 'input',
            type: 'input',
            hide:true, // 設定為true,隱藏, 我們發現是通過css隱藏的
            templateOptions: {
                label: 'Input',
                placeholder: 'Input placeholder',
                required: true,
            },
        },
        ]

renderFormlyFieldElement

是否呈現渲染每項中<form-field> 元件裡面的dom

預設為true

// 我們發現裡面的內容都被隱藏啦  
this.config.extras.renderFormlyFieldElement = false;

我們發現form-field 元件的內容會提取到外層

非同步修改值

{
            key: 'input',
            type: 'input',
            props: {
                label: '測試1'
            },
            expressions: {
                'props.label':timer(2000).pipe(
                    map(()=>'修改為2')
                )
            },
}

修改一項的值

    ngOnInit(): void {
        setTimeout(()=>{
            this.fields[0]?.formControl?.setValue('aaaa');
            console.log(this.model);
        },2000)
    }

key 支援括號/點的方式拿到屬性

   fields: FormlyFieldConfig[] = [{
            key: 'name[0].age',
            type: 'textarea',
            className: 'flex-1',
        }]

    model = {name: [{age: 'sexName'}]};

下拉框

fields: FormlyFieldConfig[] = [
    // 多選
        {
            key: 'Select',
            type: 'select',
            props: {
                label: 'Select',
                placeholder: 'Placeholder',
                description: 'Description',
                required: true,
                options: [
                    { value: 1, label: 'Option 1' },
                    { value: 2, label: 'Option 2' },
                    { value: 3, label: 'Option 3' },
                    { value: 4, label: 'Option 4', disabled: true },
                ],
            },
        },
// 多選
        {
            key: 'select_multi',
            type: 'select',
            props: {
                label: 'Select Multiple',
                placeholder: 'Placeholder',
                description: 'Description',
                required: true,
                multiple: true,
                selectAllOption: 'Select All',
                options: [
                    { value: 1, label: 'Option 1' },
                    { value: 2, label: 'Option 2' },
                    { value: 3, label: 'Option 3' },
                    { value: 4, label: 'Option 4', disabled: true },
                ],
            },
        },
    ];

單選

 fields: FormlyFieldConfig[] = [
    {
      key: 'Radio',
      type: 'radio',
      props: {
        label: 'Radio',
        placeholder: 'Placeholder',
        description: 'Description',
        required: true,
        options: [
          { value: 1, label: 'Option 1' },
          { value: 2, label: 'Option 2' },
          { value: 3, label: 'Option 3' },
          { value: 4, label: 'Option 4', disabled: true },
        ],
      },
    },
  ];

複選

fields: FormlyFieldConfig[] = [
    {
      key: 'Checkbox',
      type: 'checkbox',
      props: {
        label: 'Accept terms',
        description: 'In order to proceed, please accept terms',
        pattern: 'true',
        required: true,
      },
      validation: {
        messages: {
          pattern: 'Please accept the terms',
        },
      },
    },
  ];
fields: FormlyFieldConfig[] = [
    {
      key: 'Textarea',
      type: 'textarea',
      props: {
        label: 'Textarea',
        placeholder: 'Placeholder',
        description: 'Description',
        required: true,
      },
    },
  ];
fields: FormlyFieldConfig[] = [
    {
      key: 'Input',
      type: 'input',
      props: {
        label: 'Input',
        placeholder: 'Placeholder',
        description: 'Description',
        required: true,
      },
    },
  ];