首页 > 解决方案 > 创建了自定义单选按钮库,但在与反应形式绑定时未检查单选按钮

问题描述

角度库我正在尝试为自定义单选按钮创建一个库,其作用类似于警告(颜色黄点)错误(颜色红点)信息(颜色蓝点)和成功(颜色绿点)没有角度库它会工作正常,之后,我将我的 css 和模板移动到库中,但现在它无法正常工作

我从反应式中提供了价值,但它没有按预期检查我还在最后添加了截图我所期望的和我目前得到的

单选按钮.html

    <label class="container" >
        <input type="radio" #radioButton [name]="lbName" (click)="onClick($event)" [value]="lbValue"  
                                              [disabled]="lbDisabled" />
        <span [class]="className"></span>
        <span>
            <ng-content></ng-content>
        </span>
    </label>

单选按钮.scss

/* The container */
.container {
    display: block;
    position: relative;
    padding-left: 35px;
    margin-bottom: 12px;
    cursor: pointer;
    font-size: 14px;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

/* Hide the browser's default radio button */
.container input {
    position: absolute;
    opacity: 0;
    cursor: pointer;
}

/* Create a custom radio button */
.lb-radio {
    position: absolute;
    top: 0;
    left: 0;
    height: 20px;
    width: 20px;
    background-color: #eee;
    border-radius: 50%;
}

.container input ~ .lb-radio--warning {
    border: 1px solid darkorange;
}

.container input ~ .lb-radio--success {
    border: 1px solid green;
}

.container input ~ .lb-radio--error {
    border: 1px solid red;
}

.container input ~ .lb-radio--info {
    border: 1px solid blue;
}

/* When the radio button is checked, add a blue background */
.container input[type='radio']:checked ~ .lb-radio--warning {
    background-color: darkorange;
}

.container input[type='radio']:checked ~ .lb-radio--success {
    background-color: green;
}

.container input[type='radio']:checked ~ .lb-radio--error {
    background-color: red;
}

.container input[type='radio']:checked ~ .lb-radio--info {
    background-color: blue;
}

/* Create the indicator (the dot/circle - hidden when not checked) */
.lb-radio::after {
    content: '';
    position: absolute;
    display: none;
}

/* Style the indicator (dot/circle) */
.container .lb-radio::after {
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: white;
}

/* Show the indicator (dot/circle) when checked */
.container input[type='radio']:checked ~ .lb-radio::after {
    display: block;
}

.container input[type='radio']:disabled + span {
    border: 1px solid grey;
}

.container input[type='radio']:disabled ~ span {
    background-image: none;
    background-color: none;
    color: grey;
    cursor: not-allowed;
}

.container input[type='radio']:checked:disabled + span {
    border: 1px solid grey;
    background-color: grey;
}

.container input[type='radio']:checked:disabled ~ span {
    background-image: none;
    color: grey;
    cursor: not-allowed;
}

单选按钮.ts

import { Component, OnInit, Output, Input, EventEmitter, forwardRef} from '@angular/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

@Component({
    selector: 'lb-radio-button, [lb-radio-button]',
    templateUrl: './radio-button.component.html',
    styleUrls: ['./radio-button.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => RadioButtonComponent),
            multi: true
        }
    ]
})
export class RadioButtonComponent implements OnInit,  ControlValueAccessor {
    @ViewChild('radioButton') radioButton: ElementRef;
    className = 'lb-radio';
    value = '';
    isDisabled: boolean;
    @Output() checkBoxClick: EventEmitter<boolean> = new EventEmitter<boolean>();

    @Input() lbRole = 'info';
    @Input() lbName = 'radioButton';

    @Input()
    set lbDisabled(value: boolean) {
        this.isDisabled = coerceBooleanProperty(value);
    }
    get lbDisabled() {
        return this.isDisabled;
    }

    @Input()
    set lbValue(value: string) {
        this.writeValue(value);
        this.onChange(value);
        this.onTouched();
    }

    get lbValue(): string {
        return this.value;
    }

    onChange: any = () => {};
    onTouched: any = () => {};

    ngOnInit() {
        this.className += ` lb-radio--${this.lbRole}`;
    }

    onClick($event): boolean {
        if (this.lbDisabled) {
            return false;
        }
        this.writeValue($event.target.value);
        this.checkBoxClick.emit($event.target.value);
    }

    registerOnChange(fn) {
        this.onChange = fn;
      }

      writeValue(value) {
        if (value)
          this.value = value;
      }

      registerOnTouched(fn) {
        this.onTouched = fn;
      }
}

并使用了图书馆,但男性单选按钮没有按预期检查

**implementation.html**

<form [formGroup]="radioButtonForm">
    <p lb-radio-button [lbValue]="'Male'" [formControlName]="nameKey.gender" [lbRole]="'success'" [lbName]="'gender'">
       Male success radio button
    </p>
    <p lb-radio-button [lbValue]="'Female'" [formControlName]="nameKey.gender" [lbRole]="'info'" [lbName]="'gender'">
       Female info radio button
    </p>
    <div lb-button (buttonClick)="onSubmit()" type="submit" lbType="primary">Submit Form</div>
</form>

**implementation.ts**

export class RadioButtonComponent implements OnInit {
    radioButtonForm: FormGroup;

    nameKey =  {
        gender: 'gender',
    };

    ngOnInit() {
        this.radioButtonForm = this.formBuilder.group({
            gender: ['Male', Validators.required],
        });
    }

    onSubmit() {
        alert(JSON.stringify(this.radioButtonForm.value))
    }

}

预期的

在此处输入图像描述

目前我得到的

在此处输入图像描述

标签: angularangular-reactive-formsreactive-formsangular-library

解决方案


Stackblitz 演示(该演示包含比以下建议中描述的基础知识更多的信息)。

使用FormControl绑定到多个组件的同一个实例并不是我喜欢的。如果您允许我提出一个建议,顺便说一句,您可以通过以下方式解决您的问题FormControl:您为什么不通过创建一个仅用于表单的“RadioGroupControl”来利用您的组件,以便您可以关联aFormControl到组,而不是使用单独的控件进行操作。

你可以做什么:

1) 创建一个LbRadioButtonGroupComponent

它的模板将是一个非常简单的模板:

selector: 'lb-radio-group-control',
template: '<ng-content></ng-content>'

正如选择器所说,该组件应该仅用于对表单内的单选按钮进行分组。它应该只是一个包装器。

在其打字稿中,获取 RadioButtons 的所有实例。

@ContentChildren(RadioButtonComponent, { descendants: true })
_radioButtons: QueryList<RadioButtonComponent>;

然后订阅每个内部的事件发射器RadioButtonComponent。您需要发出的不仅仅是一个布尔值,以便可以在您获得传入值时LbRadioButtonGroupComponent设置正确的单选按钮checked状态(由主机使用,或类似的东西设置)。我建议一个包含 2 个属性的属性:(clicked )和(一个方便的属性,如果你愿意,你可以去掉它)。所以我们有:truesetValue()patchValue()RadioButtonChangetargetinputvalue

ngAfterViewInit() {
  const observableList: Observable<RadioButtonChange>[] = [];
  this._radioButtons.map((rb: RadioButtonComponent) => 
    observableList.push(rb.checkBoxClick));

  merge(...observableList)
    .pipe(
      filter(Boolean),
      takeUntil(this._destroy$)
    )
    .subscribe((value: any) => {
      this._onTouch();
      this._onChange(value ? value.value : null);
    });
}

然后我们只需要一种方法来设置当前选中的按钮。这里的问题只是时间问题,因为我们真的不知道何时调用该方法来设置一个值,我们必须确保我们的QueryList方法已经被执行并被this._radioButtons填充。一旦我们确定发生这种情况,以下方法应将右侧单选按钮设置为checked

private _setGroupValue(value: any) {
  this._radioButtons.forEach(
    rb => (rb.radioButton.nativeElement.checked =
        rb.radioButton.nativeElement.value === value)
  );
}

2)在将它们作为一个组使用时,将它们RadioButtonComponent包装起来RadioButtonGroupComponent

一旦我们有了这些部分,我们就可以像这样使用我们的组件:

<form [formGroup]="radioButtonForm">
  <lb-radio-group-control formControlName="gender">
    <p lb-radio-button [lbValue]="'Male'" 
                       [lbRole]="'success'">
      Male success radio button
    </p>
    <p lb-radio-button [lbValue]="'Female'" 
                       [lbRole]="'info'">
      Female info radio button
    </p>
  </lb-radio-group>
  <div lb-button (buttonClick)="onSubmit()" 
                 type="submit" 
                 lbType="primary">Submit Form</div>
</form>

由于我们已经拥有lb-radio-buttons 的实例,因此无需为它们设置名称:我们可以在LbRadioButtonGroupComponent. 当然,您可以通过设置名称或为每个单选按钮(错误地)设置不同名称的方式来执行此操作。

在此处输入图像描述

旁注

在 Stackblitz 上的代码中,您会发现更完整的代码,其中包含一些验证,例如检查组内是否没有RadioButtonComponent绑定到 a FormControl,或者开发人员是否忘记绑定LbRadioButtonGroupComponent到 a FormControl

好吧,这只是避免将其绑定FormControl到模板中的多个组件的总体思路。它不完整,需要表单验证、禁用表单控件以及通过 an 设置组值的能力@Input() value(我开始在演示中编写它,但我很着急,我想你已经明白了) .


推荐阅读