创建了自定义单选按钮库,但在与反应形式绑定时没有检查单选按钮
Created custom radio button library, but did not check radio button while binding with reactive-form
Angular图书馆
我正在尝试为自定义单选按钮创建一个库,其作用类似于警告(颜色黄点)错误(颜色红点)信息(颜色蓝点)和成功(颜色绿点)没有 angular 库它会工作很好,之后我将我的 css 和模板移到库中,但现在它不起作用
我已经从 reactive 中提供了价值,但它没有像预期的那样检查我还在最后添加了屏幕截图,这是我所期望的和我目前得到的
收音机-button.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>
收音机-button.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;
}
收音机-button.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))
}
}
预计
目前我得到的
不要在 lbValue 中调用 writeValue setter,它会覆盖 FormControl 设置的值。
@Input lbValue;
将值传播到父表单,每当自定义控件值更改时,您必须调用存储的 onChange 回调函数。
onClick($event): boolean {
if (this.lbDisabled) {
return false;
}
this.onChange($event.target.value);
this.onTouched();
this.checkBoxClick.emit($event.target.value);
}
Stackblitz demo(演示包含的信息比下面建议中描述的基础知识多一点)。
使用绑定到多个组件的相同 FormControl
实例不是我能接受的。如果您允许我提出一个建议,BTW 解决了您的问题,即未在 FormControl
中设置初始值:为什么不通过创建一个 [=78] 来利用您的组件=] 只是为了与表单一起使用,这样您就可以将 FormControl
关联到组,而不是与单独的控件一起使用。
你可以做什么:
1) 创建一个 LbRadioButtonGroupComponent
它的模板会非常简单:
selector: 'lb-radio-group-control',
template: '<ng-content></ng-content>'
正如选择器所说,该组件应该仅用于对表单内的单选按钮进行分组。它应该只是一个包装器。
在其打字稿中,获取 RadioButtons 的所有实例。
@ContentChildren(RadioButtonComponent, { descendants: true })
_radioButtons: QueryList<RadioButtonComponent>;
然后订阅每个 RadioButtonComponent
内的事件发射器。您需要发出的不仅仅是一个布尔值,以便 LbRadioButtonGroupComponent
可以在您获得传入值(由主机使用 setValue()
、patchValue()
或类似的东西)。我建议 RadioButtonChange
属性 包含 2 个属性:target
(点击的 input
)和 value
(一个方便的属性,如果你可以去掉它想要)。所以我们有:
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-button
s 的实例,因此无需为它们设置名称:我们可以在 LbRadioButtonGroupComponent
中进行设置。当然,你可以这样设置,不管你设置名称,还是给每个单选按钮设置不同的名称(错误)。
旁注
在 Stackblitz 上的代码中,您会找到更完整的代码,其中包含一些验证,例如检查组内是否没有任何 RadioButtonComponent
绑定到 FormControl
,或者开发者是否忘记将 LbRadioButtonGroupComponent
绑定到 FormControl
.
好吧,这只是避免将相同 FormControl
绑定到模板中的多个组件的总体思路。它不完整,需要诸如表单验证、表单控件禁用和通过 @Input() value
设置组值的能力(我开始在演示中编写它,但我很着急,我想你已经已经明白了)。
Angular图书馆 我正在尝试为自定义单选按钮创建一个库,其作用类似于警告(颜色黄点)错误(颜色红点)信息(颜色蓝点)和成功(颜色绿点)没有 angular 库它会工作很好,之后我将我的 css 和模板移到库中,但现在它不起作用
我已经从 reactive 中提供了价值,但它没有像预期的那样检查我还在最后添加了屏幕截图,这是我所期望的和我目前得到的
收音机-button.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>
收音机-button.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;
}
收音机-button.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))
}
}
预计
目前我得到的
不要在 lbValue 中调用 writeValue setter,它会覆盖 FormControl 设置的值。
@Input lbValue;
将值传播到父表单,每当自定义控件值更改时,您必须调用存储的 onChange 回调函数。
onClick($event): boolean {
if (this.lbDisabled) {
return false;
}
this.onChange($event.target.value);
this.onTouched();
this.checkBoxClick.emit($event.target.value);
}
Stackblitz demo(演示包含的信息比下面建议中描述的基础知识多一点)。
使用绑定到多个组件的相同 FormControl
实例不是我能接受的。如果您允许我提出一个建议,BTW 解决了您的问题,即未在 FormControl
中设置初始值:为什么不通过创建一个 [=78] 来利用您的组件=] 只是为了与表单一起使用,这样您就可以将 FormControl
关联到组,而不是与单独的控件一起使用。
你可以做什么:
1) 创建一个 LbRadioButtonGroupComponent
它的模板会非常简单:
selector: 'lb-radio-group-control',
template: '<ng-content></ng-content>'
正如选择器所说,该组件应该仅用于对表单内的单选按钮进行分组。它应该只是一个包装器。
在其打字稿中,获取 RadioButtons 的所有实例。
@ContentChildren(RadioButtonComponent, { descendants: true })
_radioButtons: QueryList<RadioButtonComponent>;
然后订阅每个 RadioButtonComponent
内的事件发射器。您需要发出的不仅仅是一个布尔值,以便 LbRadioButtonGroupComponent
可以在您获得传入值(由主机使用 setValue()
、patchValue()
或类似的东西)。我建议 RadioButtonChange
属性 包含 2 个属性:target
(点击的 input
)和 value
(一个方便的属性,如果你可以去掉它想要)。所以我们有:
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-button
s 的实例,因此无需为它们设置名称:我们可以在 LbRadioButtonGroupComponent
中进行设置。当然,你可以这样设置,不管你设置名称,还是给每个单选按钮设置不同的名称(错误)。
旁注
在 Stackblitz 上的代码中,您会找到更完整的代码,其中包含一些验证,例如检查组内是否没有任何 RadioButtonComponent
绑定到 FormControl
,或者开发者是否忘记将 LbRadioButtonGroupComponent
绑定到 FormControl
.
好吧,这只是避免将相同 FormControl
绑定到模板中的多个组件的总体思路。它不完整,需要诸如表单验证、表单控件禁用和通过 @Input() value
设置组值的能力(我开始在演示中编写它,但我很着急,我想你已经已经明白了)。