前言
- 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;
}
}
}
}
}
}