创建了自定义单选按钮库,但在与反应形式绑定时没有检查单选按钮

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);
    }

Example

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) 将您的 RadioButtonComponentRadioButtonGroupComponent 包装在表格中作为一个组

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

<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 绑定到 FormControl,或者开发者是否忘记将 LbRadioButtonGroupComponent 绑定到 FormControl.

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