angular自定义form表单元素-checkList

实际使用form的时候,最外层的form的某个表单元素可能是个组合的,这种情况如果是可多场景复用的,最好封装一个表单元素。本文以组合复选框为例来说明下自定义表单元素的过程

实现效果

展示效果

在这里插入图片描述
在这里插入图片描述

html

<form nz-form [formGroup]="form">
  <nz-form-item nz-row>
    <nz-form-label style="width: 120px;" nzRequired>{{ label }}</nz-form-label>
    <nz-form-control [nzSpan]="18" [nzErrorTip]="'至少选择一个类型'">
      <app-checkbox
        [data]="checkList"
        formControlName="type"
      ></app-checkbox>
    </nz-form-control>
  </nz-form-item>
  <div nz-row>
    <div nz-col [nzSpan]="18" style="margin-left: 120px;">
      <button nz-button nzType="primary" (click)="onVerify()">校验</button>
    </div>
  </div>
</form>
<div>
  {{formValueStr}}
</div>

ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { FormUtils } from '../../utils/form-utils';
import { CheckItem } from '../checkbox/checkbox.component';

@Component({
  selector: 'app-form-test',
  templateUrl: './form-test.component.html',
  styleUrls: ['./form-test.component.scss']
})
export class FormTestComponent implements OnInit {

  form!: FormGroup;
  label = '支出类型';
  checkList: CheckItem[] = [
    { label: '交通', value: '1' },
    { label: '日常', value: '2' },
    { label: '旅游', value: '3' },
    { label: '理财', value: '4' }
  ];
  get formValueStr(): string{
    return JSON.stringify(this.form.value);
  }

  constructor(
    private fb: FormBuilder,
  ) { }

  ngOnInit(): void {
    this.form = this.fb.group({
      type: [
        ['1'],
        (c: FormControl) => {
          console.log('value', c.value);
          return c.value && c.value.length > 0 ? null : { required: '123' };
        }
      ]
    });
  }

  onVerify(): void {
    FormUtils.verifyState(this.form);
  }
}
用到的API

ControlValueAccessor - 定义一个接口,该接口充当 Angular 表单 API 和 DOM 中的原生元素之间的桥梁。

interface ControlValueAccessor {
  // 当请求从模型到视图的编程更改时,表单 API 会调用此方法以写入视图。
  writeValue(obj: any): void

  // 注册一个回调函数,该控件的值在 UI 中更改时将调用该回调函数。
  registerOnChange(fn: any): void

  // 注册一个在初始化时由表单 API 调用的回调函数,以在失焦时更新表单模型。
  registerOnTouched(fn: any): void

  // 当控件状态更改为 “DISABLED” 或从 “DISABLED” 更改时,表单 API 要调用的函数。
  // 根据其状态,它会启用或禁用适当的 DOM 元素。
  setDisabledState(isDisabled: boolean)?: void
}
checkList组件

html

<div [formGroup]="form">
  <label
      *ngFor="let item of data;let i = index;"
      nz-checkbox
      [nzValue]="item.value"
      [formControlName]="i"
    >{{ item.label }}</label>
</div>

ts

import { ChangeDetectionStrategy, Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
export interface CheckItem {
  label: string;
  value: string;
  checked?: boolean;
}

@Component({
  selector: 'app-checkbox',
  templateUrl: './checkbox.component.html',
  styleUrls: ['./checkbox.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CheckboxComponent),
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckboxComponent implements ControlValueAccessor, OnInit {

  @Input() data: CheckItem[] = [];
  form: FormGroup = new FormGroup({});

  private _onChange = (_: any) => { };

  constructor(
    private fb: FormBuilder,
  ) { }

  reGroupValues(values: CheckItem[]) {
    let data: any[] = this.data.map((item, index) => {
      return {
        ...item,
        checked: values[index],
      };
    }).filter(item => item.checked).map(item => item.value);
    return data;
  }

  resetCpValue(values: any) {
    const cpValue: any = {};
    this.data.forEach((item, index) => {
      cpValue[index] = values.indexOf(item.value) > -1 ? true : false;
    });
    return cpValue;
  }

  ngOnInit() {
    const options: any = {};
    this.data.forEach((item, index) => {
      options[index] = [!item.checked ? false : true];
    });
    this.form = this.fb.group(options);
    // 监听控件的每一个元素change事件,然后调用 _onChange
    this.form.valueChanges.subscribe(values => {
      this._onChange(this.reGroupValues(values));
    });
  }

  // 向控件form中写入values-form通过setValue,patchValue给form赋值的时候
  writeValue(values: any) {
    if (!values) {
      return;
    }
    const cpValue: any = {};
    this.data.forEach((item, index) => {
      cpValue[index] = values.indexOf(item.value) > -1 ? true : false;
    });
    this.form.setValue(cpValue);
  }

  // 注册空间change事件-用于外部form对该控件的监听
  registerOnChange(fn: any) {
    this._onChange = fn;
  }

  registerOnTouched(fn: any) {
    // 此组件是否触碰可以不设置,无实际意思
    // 如果是封装的输入元素可以设置此操作,并在组件中某些输入blur事件中调用 _onTouched 来进行触发
    // eg this._onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    // 因为当前组件为check的组合,每个元素都可以进行disable设置,故此可以调用form的方法
    if (isDisabled) {
      this.form.disable();
    } else {
      this.form.enable();
    }
  }
}

至此可以实现本文开头截图中的效果

FormUtils工具
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';

export enum MarkType {
  DIRTY = 'dirty',
  PRISTINE = 'pristine',
}

export class FormUtils {
  static markAs(control: AbstractControl, markType: MarkType) {
    if (control instanceof FormControl && !control.dirty) {
      control.markAsDirty();
      control.updateValueAndValidity();
    } else if (control instanceof FormArray) {
      control.controls.forEach(item => {
        this.markAs(item, markType);
      });
    } else if (control instanceof FormGroup) {
      Object.keys(control.controls).forEach(key => {
        this.markAs(control.controls[key], markType);
      });
    }
  }

  static verifyState(control: AbstractControl) {
    FormUtils.markAs(control, MarkType.DIRTY);
  }

  static resetState(control: AbstractControl) {
    FormUtils.markAs(control, MarkType.PRISTINE);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值