Created custom radio button library, but did not check radio button while binding with reactive-form

£可爱£侵袭症+ 提交于 2021-01-28 14:13:18

问题


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 RadioButtonComponents 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-buttons, 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!