angular自定义题目选项列表、checkboxGroup、radioGroup组件

前言

  • angular组件库material没有checkboxGroup组件
  • 所以我们可以自定义封装一个,该组件讲checkboxGroup和radioGroup混合在了一起
  • 刚好业务需求要做一个题目选项列表效果,复习自定义表单组件

话不多说,先上效果图:

题目选项列表组件

使用:

<select-group type="radio" [options]="{label: '选项一', value: 'A'}" [formControl]="formControl"></select-group>

html代码如下:

  • formGroup 绑定自定义的 checkbox 选项列表
  • formControl 绑定 material 的radio组件
  • 通过组件入参 type 区分
<ng-container *ngIf="type === 'checkbox'; else radioTemplate">
  <form class="group" [formGroup]="checkboxGroup">
    <mat-checkbox *ngFor="let item of options" [formControlName]="item.value">
    	{{item.label}}
    </mat-checkbox>
  </form>
</ng-container>
<ng-template #radioTemplate>
  <mat-radio-group [formControl]="radioGroup">
    <mat-radio-button *ngFor="let item of options" [value]="item.value">
    	{{item.label}}
    </mat-radio-button>
  </mat-radio-group>
</ng-template>

重点在 ts 代码:

  • 注入 NG_VALUE_ACCESSOR 用于为表单控件提供 ControlValueAccessor
  • 自定义表单组件要实现接口 ControlValueAccessor 3个方法
    - writeValue 值改变时会触发,用于设置值
    - registerOnTouched 注册onTouched方法,入参 fn,我们 可以在自定义组件 focus 事件触发时,调用此 fn 触发表单的一些校验逻辑(这里没有实现)
    - registerOnChange 注册onChange方法,入参 fn,在我们的 checkboxGroup 和 radioGroup valueChange时调用此 fn,实现把值传递给使用此组件的控件
  • 我们在 ngOnChanges 里面创建 formGroup ,可以让传参 type 或者 options 切换时重新创建使用

这里组件输出值 单选是 A 多选是字符串 A,B
想要别的结果可以在调用 this._onChange(这里自定义哦)

import { Component, forwardRef, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';

interface option{
  lebal: string,
  value: string
}

@Component({
  selector: 'select-group',
  templateUrl: './select-group.component.html',
  styleUrls: ['./select-group.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SelectGroupComponent),
      multi: true
    }
  ]
})
export class SelectGroupComponent implements OnChanges, OnDestroy, ControlValueAccessor {
  constructor() { }
  
  private _destory$: Subject<void> = new Subject();
  private _onChange: Function;
  private _onTouched: Function;

  checkboxGroup: FormGroup;
  radioGroup: FormControl;

  @Input() type: 'checkbox' | 'radio' = 'checkbox';
  @Input() options: option[] = [];

  get control(): FormGroup | FormControl {
    return this.type === 'checkbox' ? this.checkboxGroup : this.radioGroup;
  }

  ngOnChanges(changes: SimpleChanges): void {
    let type = changes['type']?.currentValue ?? this.type;
    let options = changes['options']?.currentValue ?? this.options;
    if (type === 'checkbox') {
      this.checkboxGroup = new FormGroup({});
      options.forEach(option => {
        this.checkboxGroup.addControl(option.value, new FormControl(null));
      });
    } else {
      this.radioGroup = new FormControl(null);
    }
    this.control.valueChanges.pipe(
      takeUntil(this._destory$)
    ).subscribe(val => {
      this._onChange?.(type === 'checkbox' ? Object.keys(val).filter(key => val[key]).toString() : val);
    });
  }

  ngOnDestroy(): void {
    this._destory$.next();
    this._destory$.complete();
  }

  writeValue(value: any): void {
    let val = value;
    if (this.type === 'checkbox') {
      const obj = this.control.getRawValue();
      Object.keys(obj).forEach(key => {
        obj[key] = !!value?.includes(key);
      })
      val = obj;
    }
    this.control.setValue(val);
  }

  registerOnChange(fn: (_: any) => void): void {
    this._onChange = fn;
  }

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

最后css代码,material自带样式属实不好看,想要样式的可以抄:

:host {
  .group, mat-radio-group{
    display: flex;
    flex-direction: column;
    gap: 8px;
    &>*{
      box-sizing: border-box;
      padding: 3px 0 3px 8px;
      cursor: pointer;
    }
    .mat-checkbox{
      display: flex;
      align-items: center;
    }
    .mat-checkbox-checked, .mat-radio-checked{
      color: var(--theme, #0059EC);
      outline: .5px solid var(--theme, #0059EC);
      border-radius: 2px 2px 2px 2px;
      background: rgba(236,243,255,0.51);
    }
    
    ::ng-deep{
      .mat-checkbox-frame, .mat-radio-outer-circle{
        border-color: #DCDCDC;
        border-width: 1px;
      }
      .mat-checkbox-layout{
        width: 100%;
        line-height: 24px;
        .mat-checkbox-label{
          flex: 1;
        }
      }
      mat-radio-button{
        .mat-radio-container{
          width: 18px;
          height: 18px;
          .mat-radio-outer-circle{
            width: 18px;
            height: 18px;
          }
          .mat-radio-inner-circle{
            width: 18px;
            height: 18px;
          }
        }
      }
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值