问题
Angular library I am trying to created a library for custom radio button with its role like warning(color yellow dot) error(color red dot) info(color blue dot) and success(color green dot) without angular library it will work fine, After that I move my css and template to library but now it is not work is expected
I have provide value from reactive from but it does not check as expect I have also added screenshot at the end what I am expecting and what I get currently
radio-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>
radio-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;
}
radio-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;
}
}
And used the library but male radio button does not checked as expected
**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))
}
}
Expected
Currently what I get
回答1:
Stackblitz demo (the demo contains a little bit more information than the basics described in the suggestion below).
Using the same FormControl
instance bound to several components is not something I'm comfortable with. If you allow me a suggestion which BTW solves your problem with the initial value not being set in the FormControl
: why don't you leverage your component by creating a "RadioGroupControl" just to be used with forms, in such a way that you can associate a FormControl
to the group, instead of doing it with individual controls.
What you could do:
1) Create a LbRadioButtonGroupComponent
Its template would be a very simple one:
selector: 'lb-radio-group-control',
template: '<ng-content></ng-content>'
As the selector says, this component is supposed to be used only for grouping radio-buttons inside forms. It should just work as a wrapper.
Inside its typescript, grab all the instances of the RadioButtons.
@ContentChildren(RadioButtonComponent, { descendants: true })
_radioButtons: QueryList<RadioButtonComponent>;
Then subscribe to the event emitter inside each RadioButtonComponent
. You'll need to emit more than just a boolean so that LbRadioButtonGroupComponent
can set the right radio button checked
state to true
when you get an incoming value (set by the host with setValue()
, patchValue()
, or something like that). I suggest a RadioButtonChange
property containing 2 attributes: target
(the clicked input
) and value
(a convenience attribute, you can get rid of it if you want to). So we have:
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);
});
}
Then we would need just a way to set up which button is currently checked. The problem here is just a matter of timing because we don't really know when the method is gonna be called to set a value and we must make sure that our QueryList
was already executed and this._radioButtons
is populated. Once we make sure that happened the following method should set the right radio button to checked
:
private _setGroupValue(value: any) {
this._radioButtons.forEach(
rb => (rb.radioButton.nativeElement.checked =
rb.radioButton.nativeElement.value === value)
);
}
2) Wrap your RadioButtonComponent
s with RadioButtonGroupComponent
when using them in a form as a group
Once we have these pieces in place, we can use our component like this:
<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>
As we already own the instances of lb-radio-button
s, there's no need to set the name on them: we can do that in LbRadioButtonGroupComponent
. Of course, you can do it in such a way that it doesn't matter if you set the name, or set different names to each radio button (by mistake).
Side notes
In the code on Stackblitz, you'll find a more complete code, containing some validations, like a check to see whether inside the group there aren't any RadioButtonComponent
bound to a FormControl
, or whether the developer forgot to bind LbRadioButtonGroupComponent
to a FormControl
.
Well, this is just an overall idea to avoid binding the same FormControl
to several components in the template. It's not complete and needs things like form validation, form control disabling, and ability to set the group value via an @Input() value
(I started writing it in the demo, but I'm in a rush and I think you've already got the point).
回答2:
Don't call writeValue inside lbValue setter, It will override the value set by FormControl.
@Input lbValue;
propagate value to parent Form you have to call stored onChange call back function, whenever custom control value changes.
onClick($event): boolean {
if (this.lbDisabled) {
return false;
}
this.onChange($event.target.value);
this.onTouched();
this.checkBoxClick.emit($event.target.value);
}
Example
来源:https://stackoverflow.com/questions/62427877/created-custom-radio-button-library-but-did-not-check-radio-button-while-bindin